<!DOCTYPE html>
<html lang="en-US" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>MySQL性能影响因素 | VitePress</title>
    <meta name="description" content="A VitePress site">
    <link rel="preload stylesheet" href="/notebook/assets/style.3dbfd0c2.css" as="style">
    
    <script type="module" src="/notebook/assets/app.8aaa4cbe.js"></script>
    <link rel="preload" href="/notebook/assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="/notebook/assets/chunks/framework.1336c4e5.js">
    <link rel="modulepreload" href="/notebook/assets/chunks/theme.20cddc0c.js">
    <link rel="modulepreload" href="/notebook/assets/2、数据库_MySQL_MySQL核心_优化.md.36230425.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-255ec12d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-ae3e3f51></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-ae3e3f51> Skip to content </a><!--]--><!----><header class="VPNav" data-v-255ec12d data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-0937f67c><div class="container" data-v-0937f67c><div class="title" data-v-0937f67c><div class="VPNavBarTitle has-sidebar" data-v-0937f67c data-v-86d1bed8><a class="title" href="/notebook/" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="/notebook/Vue.png" alt data-v-8426fc1a><!--]--><!--[-->任硕的文档<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-0937f67c><div class="curtain" data-v-0937f67c></div><div class="content-body" data-v-0937f67c><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-0937f67c><!--[--><!----><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-0937f67c data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>Java学前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/HTML+JS.html" data-v-2f2cfafc><!--[-->HTML+JS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/CSS.html" data-v-2f2cfafc><!--[-->CSS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue2+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue2+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue3+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue3+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/React.html" data-v-2f2cfafc><!--[-->React<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>软件测试</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->测试基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95.html" data-v-2f2cfafc><!--[-->压力测试<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>多线程</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-2f2cfafc><!--[-->基础篇<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%B9%B6%E5%8F%91%E5%AE%8C%E5%96%84.html" data-v-2f2cfafc><!--[-->进阶篇<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>开发工具</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/Chrome.html" data-v-2f2cfafc><!--[-->Chrome<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->IDEA基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E6%8F%92%E4%BB%B6.html" data-v-2f2cfafc><!--[-->IDEA插件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/VS%20Code.html" data-v-2f2cfafc><!--[-->VS Code<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>消息中间件</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ.html" data-v-2f2cfafc><!--[-->RabbitMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ.html" data-v-2f2cfafc><!--[-->RocketMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka.html" data-v-2f2cfafc><!--[-->Kafka<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Canal.html" data-v-2f2cfafc><!--[-->Canal<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-0937f67c data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-0937f67c data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-0937f67c data-v-40855f84 data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-a7b5672a><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-a7b5672a><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-0937f67c data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-255ec12d data-v-5cfd5582><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-5cfd5582><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-5cfd5582><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-5cfd5582>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-5cfd5582 data-v-18201f51><button data-v-18201f51>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-255ec12d data-v-845b8fc6><div class="curtain" data-v-845b8fc6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-845b8fc6><span class="visually-hidden" id="sidebar-aria-label" data-v-845b8fc6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Java</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%9B%86%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java集合</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Linux</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Shell.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Shell脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实用脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E8%BD%AF%E4%BB%B6%E9%83%A8%E7%BD%B2.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>软件部署</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Nginx</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%AE%9E%E6%88%98%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实战篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E9%9D%A2%E8%AF%95%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>面试篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SSM</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Maven.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Maven</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Spring.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Spring</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringMVC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringMVC</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringBatch.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringBatch</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringBoot</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%BA%94%E7%94%A8%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>应用篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E8%BF%90%E7%BB%B4&amp;%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>运维&原理</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringCloud</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringCloud</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/Sentinel.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Sentinel</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringSecurity</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Mybatis & MybatisPlus</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/Mybatis.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Mybatis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/MybatisPlus.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MybatisPlus</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/JPA.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JPA</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Git & ChatGPT</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Git.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Git</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Github.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Github</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/ChatGPT.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ChatGPT</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Jenkins.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Jenkins</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Netty.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Netty</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible has-active" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>数据库</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible has-active" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MySQL</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL设计</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%90%E7%BB%B4.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL运维</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分库分表</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Redis</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis原理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis实战</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>本地缓存</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MongoDB</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E6%95%B4%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>ElasticSearch</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/1%E3%80%81ES%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/3%E3%80%81ES%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/influxdb.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>InfluxDB</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Neo4j</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>高并发 & 秒杀 & 分布式</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E5%88%86%E5%B8%83%E5%BC%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式理论</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式锁</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E7%A7%92%E6%9D%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>秒杀</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%8F%AF%E7%94%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高可用</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%B9%B6%E5%8F%91.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高并发</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>云原生</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/Docker.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Docker</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/K8S.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>K8S</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>可视化 & 监控</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%A7%E5%B1%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>可视化大屏</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/Zabbix.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Zabbix</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>学前端</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>HTML+CSS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/HTML%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>HTML基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/CSS%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>CSS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/%E7%BD%91%E9%A1%B5%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>网页进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>JS+TS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/TypeScript.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>TS基础</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>NodeJS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Vue</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E6%96%B0%E8%AF%AD%E6%B3%95.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3新语法</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue2/Vue2%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小程序</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/uniapp.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>uniapp</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>计算机基础</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>数据结构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>操作系统</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>设计模式</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>计算机网络</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/UML.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>UML</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95/LeetCode.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>LeetCode</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>项目实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>云尚办公</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E4%BA%91%E5%B0%9A%E5%8A%9E%E5%85%AC/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小兔鲜</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%871.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇1</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>地图</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>苍穹外卖</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>黑马头条</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E6%94%AF%E4%BB%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>支付</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目推荐</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0" data-v-845b8fc6 data-v-9b797284><!----><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/team.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>团队成员</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-255ec12d data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-3f215769 data-v-ff0f39c8><div class="content" data-v-ff0f39c8><div class="outline-marker" data-v-ff0f39c8></div><div class="outline-title" data-v-ff0f39c8>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-ff0f39c8><span class="visually-hidden" id="doc-outline-aria-label" data-v-ff0f39c8> Table of Contents for current page </span><ul class="root" data-v-ff0f39c8 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _notebook_2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93_MySQL_MySQL%E6%A0%B8%E5%BF%83_%E4%BC%98%E5%8C%96" data-v-6b87e69f><div><h1 id="mysql性能影响因素" tabindex="-1">MySQL性能影响因素 <a class="header-anchor" href="#mysql性能影响因素" aria-label="Permalink to &quot;MySQL性能影响因素&quot;">​</a></h1><h2 id="什么影响了数据库查询速度" tabindex="-1">什么影响了数据库查询速度 <a class="header-anchor" href="#什么影响了数据库查询速度" aria-label="Permalink to &quot;什么影响了数据库查询速度&quot;">​</a></h2><h3 id="四个因素" tabindex="-1">四个因素 <a class="header-anchor" href="#四个因素" aria-label="Permalink to &quot;四个因素&quot;">​</a></h3><blockquote><ul><li>SQL查询速度</li><li>服务器硬件</li><li>网卡流量</li><li>磁盘IO</li></ul></blockquote><h3 id="风险分析" tabindex="-1">风险分析 <a class="header-anchor" href="#风险分析" aria-label="Permalink to &quot;风险分析&quot;">​</a></h3><p>**QPS：**Queries Per Second意思是“每秒查询率”，是一台服务器每秒能够相应的查询次数，是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。</p><p>**TPS：**是<em>TransactionsPerSecond</em>的缩写，也就是事务数/秒。它是软件测试结果的测量单位。客户机在发送请求时开始计时，收到服务器响应后结束计时，以此来计算使用的时间和完成的事务个数。</p><blockquote><p>Tips：最好不要在主库上数据库备份，大型活动前取消这样的计划。</p></blockquote><ul><li>**效率低下的sql：**超高的QPS与TPS。</li><li>**大量的并发：**数据连接数被占满（max_connection默认100，一般把连接数设置得大一些）。 **并发量：**同一时刻数据库服务器处理的请求数量</li><li>**超高的CPU使用率：**CPU资源耗尽出现宕机。</li><li>**磁盘IO：**磁盘IO性能突然下降、大量消耗磁盘性能的计划任务。解决：更快磁盘设备、调整计划任务、做好磁盘维护。</li></ul><h3 id="网卡流量-如何避免无法连接数据库" tabindex="-1">网卡流量：如何避免无法连接数据库 <a class="header-anchor" href="#网卡流量-如何避免无法连接数据库" aria-label="Permalink to &quot;网卡流量：如何避免无法连接数据库&quot;">​</a></h3><blockquote><ul><li>减少从服务器的数量（从服务器会从主服务器复制日志）</li><li>进行分级缓存（避免前端大量缓存失效）</li><li>避免使用select * 进行查询</li><li>分离业务网络和服务器网络</li></ul></blockquote><h2 id="如何使用查询缓冲区" tabindex="-1">如何使用查询缓冲区？ <a class="header-anchor" href="#如何使用查询缓冲区" aria-label="Permalink to &quot;如何使用查询缓冲区？&quot;">​</a></h2><p><code>查询缓冲区可以提高查询的速度，但是这种方式只适合查询语句比较多、更新语句比较少 的情况</code>。</p><p>默认情况下查询缓冲区的大小为 0，也就是不可用。可以修改 <code>queiy_cache_size</code> 以调整查询缓冲区大小；修改 <code>query_cache_type</code> 以调整查询缓冲区的类型。</p><p>在 <code>my.cnf</code> 中修改 <code>query_cache_size</code> 和 <code>query_cache_type</code> 的值如下所示：</p><div class="language-c"><button title="Copy Code" class="copy"></button><span class="lang">c</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">mysqld</span><span style="color:#89DDFF;">]</span></span>
<span class="line"><span style="color:#A6ACCD;">query_cache_size</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> 512M </span></span>
<span class="line"><span style="color:#A6ACCD;">query_cache_type</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span>
<span class="line"><span style="color:#A6ACCD;">query_cache_type</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span></span></code></pre></div><p>表示开启查询缓冲区。</p><p>只有在查询语句中包含 <code>SQL_NO_CACHE</code> 关键字时，才不会使用查询缓冲区。可以使用 <code>FLUSH QUERY CACHE</code> 语句来刷新缓冲区，清理查询缓冲区中的碎片。</p><h2 id="什么影响了mysql性能-非常重要" tabindex="-1">什么影响了MySQL性能（非常重要） <a class="header-anchor" href="#什么影响了mysql性能-非常重要" aria-label="Permalink to &quot;什么影响了MySQL性能（非常重要）&quot;">​</a></h2><h3 id="影响性能的几个方面" tabindex="-1">影响性能的几个方面 <a class="header-anchor" href="#影响性能的几个方面" aria-label="Permalink to &quot;影响性能的几个方面&quot;">​</a></h3><blockquote><ul><li><p>服务器硬件</p></li><li><p>服务器系统（系统参数优化）</p></li><li><p>存储引擎</p></li><li><ul><li>MyISAM：不支持事务，表级锁</li><li>InnoDB: 支持事务，支持行级锁，事务ACID</li></ul></li><li><p>数据库参数配置。</p></li><li><p>数据库结构设计和SQL语句。（重点优化）</p></li></ul></blockquote><h3 id="如何选择正确的存储引擎" tabindex="-1">如何选择正确的存储引擎 <a class="header-anchor" href="#如何选择正确的存储引擎" aria-label="Permalink to &quot;如何选择正确的存储引擎&quot;">​</a></h3><blockquote><ul><li>事务</li><li>备份(Innodb免费在线备份)</li><li>崩溃恢复</li><li>存储引擎的特有特性</li></ul></blockquote><p>总结:Innodb大法好。</p><p><strong>注意：尽量别使用混合存储引擎，比如回滚会出问题在线热备问题。</strong></p><h3 id="内存配置参数" tabindex="-1">内存配置参数 <a class="header-anchor" href="#内存配置参数" aria-label="Permalink to &quot;内存配置参数&quot;">​</a></h3><p><strong>内存的使用上限不能超过物理内存，否则容易造成内存溢出；</strong></p><p><strong>对于32位操作系统，MySQL只能使用3G以下的内存。</strong></p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 定义了每个线程排序缓存区的大小，MySQL在有查询、</span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 需要做排序操作时才会为每个缓冲区分配内存（直接分配该参数的全部内存）；</span></span>
<span class="line"><span style="color:#A6ACCD;">sort_buffer_size </span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 定义了每个线程所使用的连接缓冲区的大小，如果一个查询关联了多张表，</span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> MySQL会为每张表分配一个连接缓冲，导致一个查询产生了多个连接缓冲；</span></span>
<span class="line"><span style="color:#A6ACCD;">join_buffer_size </span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 定义了当对一张MyISAM进行全表扫描时所分配读缓冲池大小，</span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> MySQL有查询需要时会为其分配内存，其必须是</span><span style="color:#F78C6C;">4k</span><span style="color:#A6ACCD;">的倍数；</span></span>
<span class="line"><span style="color:#A6ACCD;">read_buffer_size </span></span>
<span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;">索引缓冲区大小，MySQL有查询需要时会为其分配内存，只会分配需要的大小。</span></span>
<span class="line"><span style="color:#A6ACCD;">read_rnd_buffer_size</span></span></code></pre></div><p>注意：以上四个参数是为一个线程分配的，如果有100个连接，那么需要×100。</p><p><strong>如何为缓存池分配内存</strong></p><p>Innodb_buffer_pool_size，定义了Innodb所使用缓存池的大小，对其性能十分重要，必须足够大，但是过大时，使得Innodb 关闭时候需要更多时间把脏页从缓冲池中刷新到磁盘中；</p><p>key_buffer_size，定义了MyISAM所使用的缓存池的大小，由于数据是依赖存储操作系统缓存的，所以要为操作系统预留更大的内存空间；</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10240</span></span>
<span class="line"><span style="color:#A6ACCD;">select sum</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">index_length</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> from information_schema</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">tables where engine</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">myisam</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">;</span></span></code></pre></div><p>注意：即使开发使用的表全部是Innodb表，也要为MyISAM预留内存，因为MySQL系统使用的表仍然是MyISAM表。</p><p>max_connections 控制允许的最大连接数，一般2000更大。</p><p><strong>不要使用外键约束保证数据的完整性。</strong></p><h3 id="性能优化顺序从上到下" tabindex="-1">性能优化顺序从上到下 <a class="header-anchor" href="#性能优化顺序从上到下" aria-label="Permalink to &quot;性能优化顺序从上到下&quot;">​</a></h3><blockquote><ul><li>数据库结构设计和SQL语句</li><li>数据库存储引擎的选择和参数配置</li><li>系统选择及优化</li><li>硬件升级</li></ul></blockquote><h1 id="数据库性能优化方案" tabindex="-1">数据库性能优化方案 <a class="header-anchor" href="#数据库性能优化方案" aria-label="Permalink to &quot;数据库性能优化方案&quot;">​</a></h1><p>毫不夸张的说咱们后端工程师，无论在哪家公司，呆在哪个团队，做哪个系统，遇到的第一个让人头疼的问题绝对是数据库性能问题。</p><p>如果我们有一套成熟的方法论，能让大家快速、准确的去选择出合适的优化方案，我相信能够快速准备解决咱么日常遇到的 80% 甚至 90% 的性能问题。</p><p>从解决问题的角度出发，我们得先了解到问题的原因；其次我们得有一套思考、判断问题的流程方式，让我们合理的站在哪个层面选择方案。</p><p>最后从众多的方案里面选择一个适合的方案进行解决问题，找到一个合适的方案的前提是我们自己对各种方案之间的优缺点、场景有足够的了解，没有一个方案是完全可以通吃通用的，软件工程没有银弹。</p><p>下文的我工作多年以来，曾经使用过的八大方案，结合了平常自己学习收集的一些资料，以系统、全面的方式整理成了这篇博文，也希望能让一些有需要的同行在工作上、成长上提供一定的帮助。</p><h2 id="为什么数据库会慢" tabindex="-1">为什么数据库会慢？ <a class="header-anchor" href="#为什么数据库会慢" aria-label="Permalink to &quot;为什么数据库会慢？&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011800746.png" alt="image-20220801180009661" style="zoom:50%;"><p>无论是关系型数据库还是 NoSQL，任何存储系统决定于其查询性能的主要有三种：</p><ul><li>查找的时间复杂度</li><li>数据总量</li><li>高负载</li></ul><p>而决定于查找时间复杂度主要有两个因素：</p><ul><li>查找算法</li><li>存储数据结构</li></ul><p>无论是哪种存储，数据量越少，自然查询性能就越高，随着数据量增多，资源的消耗（CPU、磁盘读写繁忙）、耗时也会越来越高。</p><p>从关系型数据库角度出发，索引结构基本固定是 B+Tree，时间复杂度是 O(log n)，存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。</p><p>而高负载造成原因有高并发请求、复杂查询等，导致 CPU、磁盘繁忙等，而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011822693.png" alt="image-20220801182216597" style="zoom:50%;"><h2 id="应该站在哪个层面思考优化" tabindex="-1">应该站在哪个层面思考优化？ <a class="header-anchor" href="#应该站在哪个层面思考优化" aria-label="Permalink to &quot;应该站在哪个层面思考优化？&quot;">​</a></h2><h3 id="优化层面" tabindex="-1">优化层面 <a class="header-anchor" href="#优化层面" aria-label="Permalink to &quot;优化层面&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011823680.png" alt="image-20220801182344579" style="zoom:50%;"><p>从上图可见，自顶向下的一共有四层，分别是硬件、存储系统、存储结构、具体实现。</p><p>层与层之间是紧密联系的，每一层的上层是该层的载体；因此越往顶层越能决定性能的上限，同时优化的成本也相对会比较高，性价比也随之越低。</p><p>以最底层的具体实现为例，那么索引的优化的成本应该是最小的，可以说加了索引后无论是 CPU 消耗还是响应时间都是立竿见影降低。</p><p>然而一个简单的语句，无论如何优化加索引也是有局限的，当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考，思考是否从物理表设计的层面出发优化（如分库分表、压缩数据量等），如果是文档型数据库得思考下文档聚合的结果。</p><p>如果在存储结构这层优化得没效果，得继续往再上一次进行考虑，是否关系型数据库应该不适合用在现在得业务场景？如果要换存储，那么得换怎样得 NoSQL?</p><p>所以咱们优化的思路，出于性价比的优先考虑具体实现，实在没有优化空间了再往上一层考虑。当然如果公司有钱，直接使用钞能力，绕过了前面三层，这也是一种便捷的应急处理方式。</p><p>该篇文章不讨论顶与底的两个层面的优化，主要从存储结构、存储系统中间两层的角度出发进行探讨。</p><h3 id="完整优化思路" tabindex="-1">完整优化思路 <a class="header-anchor" href="#完整优化思路" aria-label="Permalink to &quot;完整优化思路&quot;">​</a></h3><p>SQL优化主要分4个方向：<code>SQL语句跟索引</code>、<code>表结构</code>、<code>系统配置</code>、<code>硬件</code>。</p><p>总优化思路就是<strong>最大化利用索引</strong>、<strong>尽可能避免全表扫描</strong>、<strong>减少无效数据的查询</strong>：</p><blockquote><p>1、减少数据访问：设置<code>合理的字段类型</code>，启用压缩，通过索引访问等减少磁盘 IO。</p><p>2、返回更少的数据：只<code>返回需要</code>的字段和数据分页处理，减少磁盘 IO 及网络 IO。</p><p>3、减少交互次数：<code>批量</code> DML 操作，函数存储等减少数据连接次数。</p><p>4、减少服务器 CPU 开销：<strong>尽量减少数据库排序操作以及全表查询</strong>，减少 CPU 内存占用 。</p><p>5、分表分区：使用<code>表分区</code>，可以增加并行操作，更大限度利用 CPU 资源。</p></blockquote><p><strong>SQL语句优化大致举例</strong>：</p><blockquote><p>1、合理建立覆盖索引：可以有效减少回表。</p><p>2、union，or，in都能命中索引，建议使用in</p><p>3、负向条件(!=、&lt;&gt;、not in、not exists、not like 等) 索引不会使用索引，建议用in。</p><p>4、在列上进行运算或使用函数会使索引失效，从而进行全表扫描</p><p>5、小心隐式类型转换，原字符串用整型会触发<code>CAST</code>函数导致索引失效。原int用字符串则会走索引。</p><p>6、不建议使用%前缀模糊查询。</p><p>7、多表关联查询时，小表在前，大表在后。在 MySQL 中，执行 from 后的表关联查询是从左往右执行的(Oracle 相反)，第一张表会涉及到全表扫描。</p><p>8、调整 Where 字句中的连接顺序，MySQL 采用从左往右，自上而下的顺序解析 where 子句。根据这个原理，应将过滤数据多的条件往前放，最快速度缩小结果集。</p></blockquote><p><strong>SQL调优大致思路</strong></p><p>1、先用慢查询日志定位具体需要优化的sql</p><p>2、使用 <a href="https://mp.weixin.qq.com/s?__biz=MzI4NjI1OTI4Nw==&amp;mid=2247488546&amp;idx=1&amp;sn=732ca84abf572196ddf76597fe096969&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">explain</a> 执行计划查看索引使用情况</p><p>3、重点关注(一般情况下根据这4列就能找到索引问题)：</p><blockquote><p>1、key（查看有没有使用索引）</p><p>2、key_len（查看索引使用是否充分）</p><p>3、type（查看索引类型）</p><p>4、Extra（查看附加信息：排序、临时表、where条件为false等）</p></blockquote><p>4、根据上1步找出的索引问题优化sql 5、再回到第2步</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208131655894.png" alt="image-20220813165537763" style="zoom:50%;"><p><strong>表结构优化</strong>：</p><blockquote><p>1、尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT，如果非负则加上UNSIGNED 。</p><p>2、VARCHAR的长度只分配真正需要的空间 。</p><p>3、尽量使用TIMESTAMP而非DATETIME 。</p><p>4、单表不要有太多字段，建议在20以内。</p><p>5、避免使用NULL字段，很难查询优化且占用额外索引空间。字符串默认为&#39;&#39;。</p></blockquote><p><strong>读写分离</strong>：</p><blockquote><p>只在主服务器上写，只在从服务器上读。对应到数据库集群一般都是一主一从、一主多从。业务服务器把需要写的操作都写到主数据库中，读的操作都去从库查询。主库会同步数据到从库保证数据的一致性。一般 <a href="https://mp.weixin.qq.com/s?__biz=MzA5NDIzNzY1OQ==&amp;mid=2735617707&amp;idx=2&amp;sn=6fd038b3385c1175a6efd4ef00543e35&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">读写分离</a> 的实现方式有两种：<code>代码封装</code>跟<code>数据库中间件</code>。</p></blockquote><p><strong>分库分表</strong>：<a href="https://mp.weixin.qq.com/s?__biz=MzkzNTEwOTAxMA==&amp;mid=2247484479&amp;idx=1&amp;sn=97358231f0f7086f0056fc5bb4e8afff&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">分库分表</a>分为垂直和水平两个方式，一般是<code>先垂直后水平</code>。</p><blockquote><p>1、<code>垂直分库</code>：将应用分为若干模块，比如订单模块、用户模块、商品模块、支付模块等等。其实就是微服务的理念。</p><p>2、<code>垂直分表</code>：一般将不常用字段跟数据较大的字段做拆分。</p><p>3、<code>水平分表</code>：根据场景选择什么字段作分表字段，比如淘宝日订单1000万，用userId作分表字段，数据查询支持到最近6个月的订单，超过6个月的做归档处理，那么6个月的数据量就是18亿，分1024张表，每个表存200W数据，hash(userId)%100找到对应表格。</p><p>4、<code>ID生成器</code>：<a href="https://mp.weixin.qq.com/s?__biz=MzI4NjI1OTI4Nw==&amp;mid=2247485459&amp;idx=1&amp;sn=9baf434bdeebe98be60bcde7df702f22&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">分布式ID</a> 需要跨库全局唯一方便查询存储-检索数据，确保唯一性跟数字递增性。</p></blockquote><p>目前主要流行的分库分表工具 就是<code>Mycat</code>和<code>sharding-sphere</code>。</p><p><strong>TiDB</strong>：开源<code>分布式</code>数据库，结合了传统的 RDBMS 和NoSQL 的最佳特性。TiDB 兼容 MySQL，<code>支持无限的水平扩展</code>，具备强一致性和高可用性。TiDB 的目标是为 OLTP(Online Transactional Processing) 和 OLAP (Online Analytical Processing) 场景提供一站式的解决方案。TiDB 具备如下核心特点</p><blockquote><p>1、支持 MySQL 协议（开发接入成本低）。</p><p>2、100% 支持事务（数据一致性实现简单、可靠）。</p><p>3、无限水平拓展（不必考虑分库分表），不停服务。</p><p>4、TiDB 支持和 MySQL 的互备。</p><p>5、遵循jdbc原则，学习成本低，强关系型，强一致性，不用担心主从配置，不用考虑分库分表，还可以无缝动态扩展。</p></blockquote><p>适合：</p><blockquote><p>1、原业务的 MySQL 的业务遇到单机容量或者性能瓶颈时，可以考虑使用 TiDB 无缝替换 MySQL。</p><p>2、大数据量下，MySQL 复杂查询很慢。</p><p>3、大数据量下，数据增长很快，接近单机处理的极限，不想分库分表或者使用数据库中间件等对业务侵入性较大、对业务有约束的 Sharding 方案。</p><p>4、大数据量下，有高并发实时写入、实时查询、实时统计分析的需求。5、有分布式事务、多数据中心的数据 100% 强一致性、auto-failover 的高可用的需求。</p></blockquote><p>不适合：</p><blockquote><p>1、单机 MySQL 能满足的场景也用不到 TiDB。</p><p>2、数据条数少于 5000w 的场景下通常用不到 TiDB，TiDB 是为大规模的数据场景设计的。</p><p>3、如果你的应用数据量小（所有数据千万级别行以下），且没有高可用、强一致性或者多数据中心复制等要求，那么就不适合使用 TiDB。</p></blockquote><h2 id="八大方案总结" tabindex="-1">八大方案总结 <a class="header-anchor" href="#八大方案总结" aria-label="Permalink to &quot;八大方案总结&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011824093.png" alt="image-20220801182359980" style="zoom:50%;"><p>数据库的优化方案核心本质有三种：减少数据量、用空间换性能、选择合适的存储系统，这也对应了开篇讲解的慢的三个原因：数据总量、高负载、查找的时间复杂度。</p><p>这里大概解释下收益类型：短期收益，处理成本低，能紧急应对，久了则会有技术债务；长期收益则跟短期收益相反，短期内处理成本高，但是效果能长久使用，扩展性会更好。</p><p>静态数据意思是，相对改动频率比较低的，也无需过多联表的，where 过滤比较少。动态数据与之相反，更新频率高，通过动态条件筛选过滤。</p><h3 id="减少数据量" tabindex="-1">减少数据量 <a class="header-anchor" href="#减少数据量" aria-label="Permalink to &quot;减少数据量&quot;">​</a></h3><p>减少数据量类型共有四种方案：数据序列化存储、数据归档、中间表生成、分库分表。</p><p>就如上面所说的，无论是哪种存储，数据量越少，自然查询性能就越高，随着数据量增多，资源的消耗（CPU、磁盘读写繁忙）、耗时也会越来越高。</p><p>目前市面上的 NoSQL 基本上都支持分片存储，所以其天然分布式写的能力从数据量上能得到非常的解决方案。</p><p>而关系型数据库，查找算法与存储结构是可以优化的空间比较少，因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化，因此本类型的优化方案主要针对关系型数据库进行处理。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011824247.png" alt="image-20220801182422175" style="zoom:50%;"><h3 id="数据归档" tabindex="-1">数据归档 <a class="header-anchor" href="#数据归档" aria-label="Permalink to &quot;数据归档&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011824988.png" alt="image-20220801182432919" style="zoom:67%;"><p>注意点：别一次性迁移数量过多，建议低频率多次限量迁移。像 MySQL 由于删除数据后是不会释放空间的，可以执行命令 OPTIMIZE TABLE 释放存储空间，但是会锁表，如果存储空间还满足，可以不执行。</p><p>建议优先考虑该方案，主要通过数据库作业把非热点数据迁移到历史表，如果需要查历史数据，可新增业务入口路由到对应的历史表（库）。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011824813.png" alt="image-20220801182451728" style="zoom:67%;"><p>在数据库以序列化存储的方式，对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式，特别是对于一些 M*N 的数据量的业务场景，如果以 M 作为主表优化，那么就可以把数据量维持最多是 M 的量级。</p><p>另外像订单的地址信息，这种业务一般是不需要根据里面的字段检索出来，也比较适合。</p><p>这种方案我认为属于一种临时性的优化方案，无论是从序列化后丢失了部份字段的查询能力，还是这方案的可优化性都是有限的。</p><h3 id="中间表-结果表" tabindex="-1">中间表（结果表） <a class="header-anchor" href="#中间表-结果表" aria-label="Permalink to &quot;中间表（结果表）&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011825007.png" alt="image-20220801182509936" style="zoom:67%;"><p>中间表（结果表）其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表，因为这张物理表存放的是通过跑批汇总后的数据，因此可以理解成根据原有的业务进行了高度的数据压缩。</p><p>以报表为例，如果一个月的源数据有数十万，我们通过调度任务以月的维度生成，那么等于把原有的数据压缩了几十万分之一。</p><p>接下来的季报和年报可以根据月报*N 来进行统计，以这种方式处理的数据，就算三年、五年甚至十年数据量都可以在接受范围之内，而且可以精确计算得到。那么数据的压缩比率是否越低越好？</p><p>下面有一段口诀：</p><ul><li>字段越多，粒度越细，灵活性越高，可以以中间表进行不同业务联表处理。</li><li>字段越少，粒度越粗，灵活性越低，一般作为结果表查询出来。</li></ul><h3 id="数据序列化存储" tabindex="-1">数据序列化存储 <a class="header-anchor" href="#数据序列化存储" aria-label="Permalink to &quot;数据序列化存储&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011825419.png" alt="image-20220801182529350" style="zoom:67%;"><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011825410.png" alt="image-20220801182546317" style="zoom:67%;"><h3 id="分库分表" tabindex="-1">分库分表 <a class="header-anchor" href="#分库分表" aria-label="Permalink to &quot;分库分表&quot;">​</a></h3><p>分库分表作为数据库优化的一种非常经典的优化方案，特别是在以前 NoSQL 还不是很成熟的年代，这个方案就如救命草一般的存在。</p><p>如今也有不少同行也会选择这种优化方式，但是从我角度来看，分库分表是一种优化成本很大的方案。</p><p>这里我有几个建议：</p><ul><li>分库分表是实在没有办法的办法，应放到最后选择。</li><li>优先选择 NoSQL 代替，因为 NoSQL 诞生基本上为了扩展性与高性能。</li><li>究竟分库还是分表？量大则分表，并发高则分库</li><li>不考虑扩容，一部做到位。因为技术更新太快了，每 3-5 年一大变。</li></ul><h4 id="拆分方式如下图" tabindex="-1">拆分方式如下图： <a class="header-anchor" href="#拆分方式如下图" aria-label="Permalink to &quot;拆分方式如下图：&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011826486.png" alt="image-20220801182600401" style="zoom:67%;"><p>只要涉及到这个拆，那么无论是微服务也好，分库分表也好，拆分的方式主要分两种：垂直拆分、水平拆分。</p><p>垂直拆分更多是从业务角度进行拆分，主要是为了降低业务耦合度；此外以 SQL Server 为例，一页是 8KB 存储，如果在一张表里字段越多，一行数据自然占的空间就越大，那么一页数据所存储的行数就自然越少，那么每次查询所需要 IO 则越高因此性能自然也越慢。</p><p>因此反之，减少字段也能很好提高性能。之前我听说某些同行的表有 80 个字段，几百万的数据就开始慢了。</p><p>水平拆分更多是从技术角度进行拆分，拆分后每张表的结构是一模一样的，简而言之就是把原有一张表的数据，通过技术手段进行分片到多张表存储，从根本上解决了数据量的问题。</p><h4 id="路由方式如下图" tabindex="-1">路由方式如下图 <a class="header-anchor" href="#路由方式如下图" aria-label="Permalink to &quot;路由方式如下图&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011829780.png" alt="image-20220801182930688" style="zoom:67%;"><p>进行水平拆分后，根据分区键（sharding key）原来应该在同一张表的数据拆解写到不同的物理表里，那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。</p><p>路由方式一般有三种区间范围、Hash、分片映射表，每种路由方式都有自己的优点和缺点，可以根据对应的业务场景进行选择。</p><p>区间范围根据某个元素的区间的进行拆分，以时间为例子，假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04，这种对于文档型、ElasticSearch 这类型的 NoSQL 也适用，无论是定位查询，还是日后清理维护都是非常的方便的。</p><p>那么缺点也明显，会因为业务独特性导致数据不平均，甚至不同区间范围之间的数据量差异很大。</p><p>Hash 也是一种常用的路由方式，根据 Hash 算法取模以数据量均匀分别存储在物理表里，缺点是对于带分区键的查询依赖特别强。</p><p>如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次，而且在分库的情况下对于 Join、聚合计算、分页等一些 RDBMS 的特性功能还无法使用。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011829579.png" alt="image-20220801182908474" style="zoom:67%;"><p>一般分区键就一个，假如有时候业务场景得用不是分区键的字段进行查询，那么难道就必须得全部扫描一遍？</p><p>其实可以使用分片映射表的方式，简单来说就是额外有一张表记录额外字段与分区键的映射关系。</p><p>举个例子，有张订单表，原本是以 UserID 作为分区键拆分的，现在希望用 OrderID 进行查询，那么得有额外得一张物理表记录了 OrderID 与 UserID 的映射关系。</p><p>因此得先查询一次映射表拿到分区键，再根据分区键的值路由到对应的物理表查询出来。</p><p>可能有些朋友会问，那这映射表是否多一个映射关系就多一张表，还是多个映射关系在同一张表。</p><p>我优先建议单独处理，如果说映射表字段过多，那跟不进行水平拆分时的状态其实就是一致的，这又跑回去的老问题。</p><h3 id="用空间换性能" tabindex="-1">用空间换性能 <a class="header-anchor" href="#用空间换性能" aria-label="Permalink to &quot;用空间换性能&quot;">​</a></h3><p>该类型的两个方案都是用来应对高负载的场景，方案有以下两种：<code>分布式缓存</code>、<code>一主多从</code>。</p><p>与其说这个方案叫用空间换性能，我认为用空间换资源更加贴切一些。因此两个方案的本质主要通数据冗余、集群等方式分担负载压力。</p><p>对于关系型数据库而言，因为他的 ACID 特性让它天生不支持写的分布式存储，但是它依然天然的支持分布式读。</p><p><img src="" alt="图片"></p><h4 id="分布式缓存" tabindex="-1">分布式缓存 <a class="header-anchor" href="#分布式缓存" aria-label="Permalink to &quot;分布式缓存&quot;">​</a></h4><p>缓存层级可以分好几种：客户端缓存、API 服务本地缓存和分布式缓存，咱们这次只聊分布式缓存。</p><p>一般我们选择分布式缓存系统都会优先选择 NoSQL 的键值型数据库，例如 Memcached、Redis，如今 Redis 的数据结构多样性，高性能，易扩展性也逐渐占据了分布式缓存的主导地位。</p><p>缓存策略也主要有很多种：Cache-Aside、Read/Wirte-Through、Write-Back，咱们用得比较多的方式主要 Cache-Aside。</p><p>具体流程可看下图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011828980.png" alt="image-20220801182826906" style="zoom:67%;"><p>我相信大家对分布式缓存相对都比较熟悉了，但是我在这里还是有几个注意点希望提醒一下大家：</p><h5 id="_1避免滥用缓存" tabindex="-1">①避免滥用缓存 <a class="header-anchor" href="#_1避免滥用缓存" aria-label="Permalink to &quot;①避免滥用缓存&quot;">​</a></h5><p>缓存应该是按需使用，从 28 法则来看，80% 的性能问题由主要的 20% 的功能引起。滥用缓存的后果会导致维护成本增大，而且有一些数据一致性的问题也不好定位。</p><p>特别像一些动态条件的查询或者分页，key 的组装是多样化的，量大又不好用 keys 指令去处理，当然我们可以用额外的一个 key 把记录数据的 key 以集合方式存储，删除时候做两次查询，先查 Key 的集合，然后再遍历 Key 集合把对应的内容删除。</p><p>这一顿操作下来无疑是非常废功夫的，谁弄谁知道。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011828690.png" alt="image-20220801182811608" style="zoom:67%;"><h5 id="_2避免缓存击穿" tabindex="-1"><strong>②避免缓存击穿</strong> <a class="header-anchor" href="#_2避免缓存击穿" aria-label="Permalink to &quot;**②避免缓存击穿**&quot;">​</a></h5><p>当缓存没有数据，就得跑去数据库查询出来，这就是缓存穿透。</p><p>假如某个时间临界点数据是空的例如周排行榜，穿透过去的无论查找多少次数据库仍然是空，而且该查询消耗 CPU 相对比较高，并发一进来因为缺少了缓存层的对高并发的应对，这个时候就会因为并发导致数据库资源消耗过高，这就是缓存击穿。数据库资源消耗过高就会导致其他查询超时等问题。</p><p>该问题的解决方案也简单，对于查询到数据库的空结果也缓存起来，但是给一个相对快过期的时间。有些同行可能又会问，这样不就会造成了数据不一致了么？</p><p>一般有数据同步的方案像分布式缓存、后续会说的一主多从、CQRS，只要存在数据同步这几个字，那就意味着会存在数据一致性的问题，因此如果使用上述方案，对应的业务场景应允许容忍一定的数据不一致。</p><h5 id="_3不是所有慢查询都适用" tabindex="-1"><strong>③不是所有慢查询都适用</strong> <a class="header-anchor" href="#_3不是所有慢查询都适用" aria-label="Permalink to &quot;**③不是所有慢查询都适用**&quot;">​</a></h5><p>一般来说，慢的查询都意味着比较吃资源的（CPU、磁盘 I/O）。</p><p>举个例子，假如某个查询功能需要 3 秒时间，串行查询的时候并没什么问题，我们继续假设这功能每秒大概 QPS 为 100，那么在第一次查询结果返回之前，接下来的所有查询都应该穿透到数据库。</p><p>也就意味着这几秒时间有 300 个请求到数据库，如果这个时候数据库 CPU 达到了 100%，那么接下来的所有查询都会超时，也就是无法有第一个查询结果缓存起来，从而还是形成了缓存击穿。</p><h4 id="一主多从" tabindex="-1">一主多从 <a class="header-anchor" href="#一主多从" aria-label="Permalink to &quot;一主多从&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011827283.png" alt="image-20220801182759208" style="zoom:67%;"><p>常用的分担数据库压力还有一种常用做法，就是读写分离、一主多从。咱们都是知道关系型数据库天生是不具备分布式分片存储的，也就是不支持分布式写，但是它天然的支持分布式读。</p><p>一主多从是部署多台从库只读实例，通过冗余主库的数据来分担读请求的压力，路由算法可有代码实现或者中间件解决，具体可以根据团队的运维能力与代码组件支持视情况选择。</p><p>一主多从在还没找到根治方案前是一个非常好的应急解决方案，特别是在现在云服务的年代，扩展从库是一件非常方便的事情，而且一般情况只需要运维或者 DBA 解决就行，无需开发人员接入。</p><p>当然这方案也有缺点，因为数据无法分片，所以主从的数据量完全冗余过去，也会导致高的硬件成本。从库也有其上限，从库过多了会主库的多线程同步数据的压力。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011827843.png" alt="image-20220801182743745" style="zoom:67%;"><h3 id="选择合适的存储系统" tabindex="-1">选择合适的存储系统 <a class="header-anchor" href="#选择合适的存储系统" aria-label="Permalink to &quot;选择合适的存储系统&quot;">​</a></h3><p>NoSQL 主要以下五种类型：键值型、文档型、列型、图型、搜素引擎，不同的存储系统直接决定了查找算法、存储数据结构，也应对了需要解决的不同的业务场景。NoSQL 的出现也解决了关系型数据库之前面临的难题（性能、高并发、扩展性等）。</p><p>例如，ElasticSearch 的查找算法是倒排索引，可以用来代替关系型数据库的低性能、高消耗的 Like 搜索（全表扫描）。而 Redis 的 Hash 结构决定了时间复杂度为 O(1)，还有它的内存存储，结合分片集群存储方式以至于可以支撑数十万 QPS。</p><p>因此本类型的方案主要有两种：CQRS、替换（选择）存储，这两种方案的最终本质基本是一样的主要使用合适存储来弥补关系型数据库的缺点，只不过切换过渡的方式会有点不一样。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011827631.png" alt="image-20220801182728530" style="zoom:50%;"><h4 id="cqrs" tabindex="-1">CQRS <a class="header-anchor" href="#cqrs" aria-label="Permalink to &quot;CQRS&quot;">​</a></h4><p>CQS（命令查询分离）指同一个对象中作为查询或者命令的方法，每个方法或者返回的状态，要么改变状态，但不能两者兼备。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011827399.png" alt="image-20220801182705320" style="zoom:67%;"><p>讲解 CQRS 前得了解 CQS，有些小伙伴看了估计还没不是很清晰，我这里用通俗的话解释：某个对象的数据访问的方法里，要么只是查询，要么只是写入（更新）。</p><p>而 CQRS（命令查询职责分离）基于 CQS 的基础上，用物理数据库来写入（更新），而用另外的存储系统来查询数据。</p><p>因此我们在某些业务场景进行存储架构设计时，可以通过关系型数据库的 ACID 特性进行数据的更新与写入，用 NoSQL 的高性能与扩展性进行数据的查询处理。</p><p>这样的好处就是关系型数据库和 NoSQL 的优点都可以兼得，同时对于某些业务不适于一刀切的替换存储的也可以有一个平滑的过渡。</p><p>从代码实现角度来看，不同的存储系统只是调用对应的接口 API，因此 CQRS 的难点主要在于如何进行数据同步。</p><h4 id="数据同步方式" tabindex="-1">数据同步方式 <a class="header-anchor" href="#数据同步方式" aria-label="Permalink to &quot;数据同步方式&quot;">​</a></h4><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011826956.png" alt="image-20220801182651880" style="zoom:67%;"><p>一般讨论到数据同步的方式主要是分推和拉：</p><ul><li>推指的是由数据变更端通过直接或者间接的方式把数据变更的记录发送到接收端，从而进行数据的一致性处理，这种主动的方式优点是实时性高。</li><li>拉指的是接收端定时的轮询数据库检查是否有数据需要进行同步，这种被动的方式从实现角度来看比推简单，因为推是需要数据变更端支持变更日志的推送的。</li></ul><p>而推的方式又分两种：CDC（变更数据捕获）和领域事件。对于一些旧的项目来说，某些业务的数据入口非常多，无法完整清晰的梳理清楚，这个时候 CDC 就是一种非常好的方式，只要从最底层数据库层面把变更记录取到就可。</p><p>对于已经服务化的项目来说领域事件是一种比较舒服的方式，因为 CDC 是需要数据库额外开启功能或者部署额外的中间件，而领域事件则不需要，从代码可读性来看会更高，也比较开发人员的维护思维模式。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202208011826445.png" alt="image-20220801182639353" style="zoom:67%;"><h4 id="替换-选择-存储系统" tabindex="-1">替换（选择）存储系统 <a class="header-anchor" href="#替换-选择-存储系统" aria-label="Permalink to &quot;替换（选择）存储系统&quot;">​</a></h4><p>因为从本质来看该模式与 CQRS 的核心本质是一样的，主要是要对 NoSQL 的优缺点有一个全面认识，这样才能在对应业务场景选择与判断出一个合适的存储系统。</p><p>这里我像大家介绍一本书马丁.福勒《NoSQL精粹》，这本书我重复看了好几遍，也很好全面介绍各种 NoSQL 优缺点和使用场景。</p><p>当然替换存储的时候，我这里也有个建议：加入一个中间版本，该版本做好数据同步与业务开关，数据同步要保证全量与增加的处理，随时可以重来，业务开关主要是为了后续版本的更新做的一个临时型的功能，主要避免后续版本更新不顺利或者因为版本更新时导致的数据不一致的情况出现。</p><p>在跑了一段时间后，验证了两个不同的存储系统数据是一致的后，接下来就可以把数据访问层的底层调用替换了。如此一来就可以平滑的更新切换。</p><h1 id="mysql-调优工具" tabindex="-1">MySQL 调优工具 <a class="header-anchor" href="#mysql-调优工具" aria-label="Permalink to &quot;MySQL 调优工具&quot;">​</a></h1><p>对于正在运行的mysql，性能如何，参数设置的是否合理，账号设置的是否存在安全隐患，你是否了然于胸呢？</p><p>俗话说工欲善其事，必先利其器，定期对你的MYSQL数据库进行一个体检，是保证数据库安全运行的重要手段，因为，好的工具是使你的工作效率倍增！</p><p>今天和大家分享几个mysql 优化的工具，你可以使用它们对你的mysql进行一个体检，生成awr报告，让你从整体上把握你的数据库的性能情况。</p><h2 id="mysqltuner-pl" tabindex="-1">mysqltuner.pl <a class="header-anchor" href="#mysqltuner-pl" aria-label="Permalink to &quot;mysqltuner.pl&quot;">​</a></h2><p>是mysql一个常用的数据库性能诊断工具，主要检查参数设置的合理性包括日志文件、存储引擎、安全建议及性能分析。针对潜在的问题，给出改进的建议。是mysql优化的好帮手。</p><p>在上一版本中，MySQLTuner支持MySQL / MariaDB / Percona Server的约300个指标。</p><blockquote><p>项目地址：<a href="https://github.com/major/MySQLTuner-perl" target="_blank" rel="noreferrer">https://github.com/major/MySQLTuner-perl</a></p></blockquote><h3 id="_1-下载" tabindex="-1">1 下载 <a class="header-anchor" href="#_1-下载" aria-label="Permalink to &quot;1 下载&quot;">​</a></h3><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">wget https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">raw</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">githubusercontent</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">com</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">major</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">MySQLTuner</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">perl</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">master</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysqltuner</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">pl</span></span></code></pre></div><h3 id="_2-使用" tabindex="-1">2 使用 <a class="header-anchor" href="#_2-使用" aria-label="Permalink to &quot;2 使用&quot;">​</a></h3><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">./</span><span style="color:#A6ACCD;">mysqltuner</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">pl </span><span style="color:#89DDFF;">--</span><span style="color:#A6ACCD;">socket </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">var</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">lib</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sock</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207291749248.png" alt="image-20220729174948043" style="zoom:67%;"><h3 id="_3-报告分析" tabindex="-1">3 报告分析 <a class="header-anchor" href="#_3-报告分析" aria-label="Permalink to &quot;3 报告分析&quot;">​</a></h3><p>1）重要关注[!!]（中括号有叹号的项）例如[!!] Maximum possible memory usage: 4.8G (244.13% of installed RAM)，表示内存已经严重用超了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207291751443.png" alt="image-20220729175153179" style="zoom:67%;"><p>2）关注最后给的建议“Recommendations ”。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207291753467.png" alt="image-20220729175324240" style="zoom:67%;"><h2 id="tuning-primer-sh" tabindex="-1">tuning-primer.sh <a class="header-anchor" href="#tuning-primer-sh" aria-label="Permalink to &quot;tuning-primer.sh&quot;">​</a></h2><p>mysql的另一个优化工具，针于mysql的整体进行一个体检，对潜在的问题，给出优化的建议。</p><blockquote><p>项目地址：<a href="https://github.com/BMDan/tuning-primer.sh" target="_blank" rel="noreferrer">https://github.com/BMDan/tuning-primer.sh</a></p></blockquote><p>目前，支持检测和优化建议的内容如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207291753216.png" alt="image-20220729175344136" style="zoom:67%;"><h3 id="_1-下载-1" tabindex="-1">1 下载 <a class="header-anchor" href="#_1-下载-1" aria-label="Permalink to &quot;1 下载&quot;">​</a></h3><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">wget https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">launchpad</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">net</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">tuning</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">primer</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">trunk</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">1.6</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">r1</span><span style="color:#89DDFF;">/+</span><span style="color:#A6ACCD;">download</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">tuning</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">primer</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sh</span></span></code></pre></div><h3 id="_2-使用-1" tabindex="-1">2 使用 <a class="header-anchor" href="#_2-使用-1" aria-label="Permalink to &quot;2 使用&quot;">​</a></h3><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">chmod </span><span style="color:#F78C6C;">777</span><span style="color:#A6ACCD;">  tuning</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">primer</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sh</span></span>
<span class="line"><span style="color:#89DDFF;">./</span><span style="color:#A6ACCD;">tuning</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">primer</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sh</span></span></code></pre></div><h4 id="_2-3-报告分析" tabindex="-1">2.3 报告分析 <a class="header-anchor" href="#_2-3-报告分析" aria-label="Permalink to &quot;2.3 报告分析&quot;">​</a></h4><p>重点查看有红色告警的选项，根据建议结合自己系统的实际情况进行修改，例如：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/eQPyBffYbufSDbY2ShvbiahwmOibhicu41WJicIu2QBMDx1HdgQtsnyuiazQiawobo9JAUUgNXibD8KA5d7dNqWte4Nvg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h2 id="pt-variable-advisor" tabindex="-1">pt-variable-advisor <a class="header-anchor" href="#pt-variable-advisor" aria-label="Permalink to &quot;pt-variable-advisor&quot;">​</a></h2><p>pt-variable-advisor 可以分析MySQL变量并就可能出现的问题提出建议。</p><h4 id="_1-安装" tabindex="-1">1 安装 <a class="header-anchor" href="#_1-安装" aria-label="Permalink to &quot;1 安装&quot;">​</a></h4><blockquote><p><a href="https://www.percona.com/downloads/percona-toolkit/LATEST/" target="_blank" rel="noreferrer">https://www.percona.com/downloads/percona-toolkit/LATEST/</a></p></blockquote><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">root</span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">localhost </span><span style="color:#89DDFF;">~]#</span><span style="color:#A6ACCD;">wget https:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">www</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">percona</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">com</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">downloads</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">percona</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">toolkit</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">3.0.13</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">binary</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">redhat</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">7</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">x86_64</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">percona</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">toolkit</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">3.0.13</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">re85ce15</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">el7</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">x86_64</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">bundle</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">tar</span></span>
<span class="line"><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">root</span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">localhost </span><span style="color:#89DDFF;">~]#</span><span style="color:#A6ACCD;">yum install percona</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">toolkit</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">3.0.13</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1.el7</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">x86_64</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">rpm</span></span></code></pre></div><h4 id="_2-使用-2" tabindex="-1">2 使用 <a class="header-anchor" href="#_2-使用-2" aria-label="Permalink to &quot;2 使用&quot;">​</a></h4><p>pt-variable-advisor是pt工具集的一个子工具，主要用来诊断你的参数设置是否合理。</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">root</span><span style="color:#89DDFF;">@</span><span style="color:#A6ACCD;">localhost </span><span style="color:#89DDFF;">~]#</span><span style="color:#A6ACCD;"> pt</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">variable</span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;">advisor localhost </span><span style="color:#89DDFF;">--</span><span style="color:#A6ACCD;">socket </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">var</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">lib</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">mysql</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">sock</span></span></code></pre></div><h4 id="_3-报告分析-1" tabindex="-1">3 报告分析 <a class="header-anchor" href="#_3-报告分析-1" aria-label="Permalink to &quot;3 报告分析&quot;">​</a></h4><p>重点关注有WARN的信息的条目，例如：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/eQPyBffYbufSDbY2ShvbiahwmOibhicu41WZLib3M64FM13TW1JliaYgMEicdy3iawJcxfM2mMb62sNSic0iconalMC04vw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h1 id="sql语句优化建议⭐" tabindex="-1">SQL语句优化建议⭐ <a class="header-anchor" href="#sql语句优化建议⭐" aria-label="Permalink to &quot;SQL语句优化建议⭐&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzU1Nzg4NjgyMw==&amp;mid=2247484646&amp;idx=1&amp;sn=1b013813a8e9d398807a14d89f5b30ac&amp;chksm=fc2fb8eecb5831f8f240ca5ef6ba9dba09c17d22275efb765a283bf49440844e8aacd1087565&amp;mpshare=1&amp;scene=23&amp;srcid=0509DtlfP7DzJwBx9srE5jkB&amp;sharer_sharetime=1652111561209&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">书写高质量SQL的30条建议，这下够用了！ (qq.com)</a></p><p><a href="https://mp.weixin.qq.com/s?__biz=MzU1Nzg4NjgyMw==&amp;mid=2247495707&amp;idx=2&amp;sn=9d5dcad3f77ff9863410b834328e8156&amp;chksm=fc2c4c13cb5bc505e5fdd587f59fcad2284d18ec5923b2e34afdfec1b9ba5e962dbcec3fd29b&amp;mpshare=1&amp;scene=23&amp;srcid=04155SkwZaRosLw9KKkB37W0&amp;sharer_sharetime=1650025089175&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">Sql优化的15个小技巧，这也太实用了叭！ (qq.com)</a></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022/202204211139668.png" alt="image-20220421113912586" style="zoom:60%;"><h2 id="_1-避免使用select-⭐" tabindex="-1">1 避免使用select * ⭐ <a class="header-anchor" href="#_1-避免使用select-⭐" aria-label="Permalink to &quot;1 避免使用select * ⭐&quot;">​</a></h2><p>“不要使用<code>SELECT *</code>”几乎已经成为了使用MySQL的一条金科玉律，就连《阿里Java开发手册》也明确表示不得使用<code>*</code>作为查询的字段列表，更是让这条规则拥有了权威的加持。</p><p>很多时候，我们写sql语句时，为了方便，喜欢直接使用<code>select *</code>，一次性查出表中所有列的数据。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>在实际业务场景中，可能我们真正需要使用的只有其中一两列。查了很多数据，但是不用，白白浪费了数据库资源，比如：内存或者cpu。此外，多查出来的数据，通过网络IO传输的过程中，也会增加数据传输的时间。</p><p>还有一个最重要的问题是：<code>select *</code>不会走<code>覆盖索引</code>，会出现大量的<code>回表</code>操作，而从导致查询sql的性能很低。</p><ul><li>不必要的磁盘I/O</li><li>加重网络时延</li><li>无法使用覆盖索引</li><li>可能拖慢JOIN连接查询</li></ul><p>那么，如何优化呢？</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>sql语句查询时，只查需要用到的列，多余的列根本无需查出来。</p><h2 id="_2-union-all和union" tabindex="-1">2 union all和union <a class="header-anchor" href="#_2-union-all和union" aria-label="Permalink to &quot;2 union all和union&quot;">​</a></h2><h3 id="_1-union" tabindex="-1">1 union <a class="header-anchor" href="#_1-union" aria-label="Permalink to &quot;1 union&quot;">​</a></h3><p><code>UNION</code>在进行表链接后会筛选掉重复的记录，所以在表链接后会对所产生的结果集进行排序运算，删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录，最常见的是过程表与历史表<code>UNION</code>。如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> username,tel </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user</span></span>
<span class="line"><span style="color:#F78C6C;">union</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> departmentname </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> department</span></span></code></pre></div><p>这个SQL在运行时先取出两个表的结果，再用排序空间进行排序删除重复的记录，最后返回结果集，如果表数据量大的话可能会导致用磁盘进行排序。推荐方案：采用<code>UNION ALL</code>操作符替代<code>UNION</code>，因为<code>UNION ALL</code>操作只是简单的将两个结果合并后就返回。</p><h3 id="_2-使用union-all代替union" tabindex="-1">2 使用union all代替union <a class="header-anchor" href="#_2-使用union-all代替union" aria-label="Permalink to &quot;2 使用union all代替union&quot;">​</a></h3><p>我们都知道sql语句使用<code>union</code>关键字后，可以获取排重后的数据。</p><p>而如果使用<code>union all</code>关键字，可以获取所有数据，包含重复的数据。</p><p><strong>反例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> boys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">union</span></span>
<span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> boys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">4</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5/202205081335686.png" alt="image-20220508133525607" style="zoom:80%;"><p>排重的过程需要遍历、排序和比较，它更耗时，更消耗cpu资源。</p><p>所以如果能用union all的时候，尽量不用union。</p><p><strong>正例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> boys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">union all</span></span>
<span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> boys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">4</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5/202205081335595.png" alt="image-20220508133559518" style="zoom:80%;"><p>除非是有些特殊的场景，比如union all之后，结果集中出现了重复数据，而业务场景中是不允许产生重复数据的，这时可以使用union。</p><h2 id="_3-小表驱动大表" tabindex="-1">3 小表驱动大表 <a class="header-anchor" href="#_3-小表驱动大表" aria-label="Permalink to &quot;3 小表驱动大表&quot;">​</a></h2><p>小表驱动大表，也就是说用小表的数据集驱动大表的数据集。</p><p>假如有order和user两张表，其中order表有10000条数据，而user表有100条数据。</p><p>这时如果想查一下，所有有效的用户下过的订单列表。</p><p>可以使用<code>in</code>关键字实现：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>也可以使用<code>exists</code>关键字实现：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">exists</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> order.user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> user.id </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>前面提到的这种业务场景，使用in关键字去实现业务需求，更加合适。</p><p>为什么呢？</p><p>因为<code>如果sql语句中包含了in关键字，则它会优先执行in里面的子查询语句</code>，<code>然后再执行in外面的语句</code>。如果in里面的数据量很少，作为条件查询速度更快。</p><p>而如果sql语句中包含了exists关键字，它优先执行exists左边的语句（即主查询语句）。然后把它作为条件，去跟右边的语句匹配。如果匹配上，则可以查询出数据。如果匹配不上，数据就被过滤掉了。</p><p>这个需求中，order表有10000条数据，而user表有100条数据。order表是大表，user表是小表。如果order表在左边，则用in关键字性能更好。</p><p>总结一下：</p><ul><li><code>in</code> 适用于左边大表，右边小表。</li><li><code>exists</code> 适用于左边小表，右边大表。</li></ul><p>不管是用in，还是exists关键字，其核心思想都是用小表驱动大表。</p><h2 id="_4-批量插入-速度更快" tabindex="-1">4 批量插入，速度更快 <a class="header-anchor" href="#_4-批量插入-速度更快" aria-label="Permalink to &quot;4 批量插入，速度更快&quot;">​</a></h2><p>如果你有一批数据经过业务处理之后，需要插入数据，该怎么办？</p><h3 id="_1-反例-多条提交" tabindex="-1">1 反例：多条提交 <a class="header-anchor" href="#_1-反例-多条提交" aria-label="Permalink to &quot;1 反例：多条提交&quot;">​</a></h3><p>在循环中逐条插入数据。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> user (id,username) </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">哪吒编程</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> user (id,username) </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">妲己</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>该操作需要多次请求数据库，才能完成这批数据的插入。</p><p>但众所周知，我们在代码中，每次远程请求数据库，是会消耗一定性能的。而如果我们的代码需要请求多次数据库，才能完成本次业务功能，势必会消耗更多的性能。</p><h3 id="_2-正例-批量提交" tabindex="-1">2 正例：批量提交 <a class="header-anchor" href="#_2-正例-批量提交" aria-label="Permalink to &quot;2 正例：批量提交&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">orderMapper.insertBatch(list):</span></span></code></pre></div><p>提供一个批量插入数据的方法。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> order(id,code,user_id) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">001</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">),(</span><span style="color:#F78C6C;">124</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">002</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>这样只需要远程请求一次数据库，sql性能会得到提升，数据量越多，提升越大。</p><p>批量插入 SQL 执行效率高的主要原因是合并后日志量 MySQL 的 binlog 和 innodb 的事务让日志减少了，降低日志刷盘的数据量和频率，从而提高了效率</p><p>但需要注意的是，不建议一次批量操作太多的数据，如果数据太多数据库响应也会很慢。批量操作需要把握一个度，建议每批数据尽量控制在500以内。如果数据多于500，则分多批次处理。</p><h2 id="_5-多用limit" tabindex="-1">5 多用limit <a class="header-anchor" href="#_5-多用limit" aria-label="Permalink to &quot;5 多用limit&quot;">​</a></h2><p>有时候，我们需要查询某些数据中的第一条，比如：查询某个用户下的第一个订单，想看看他第一次的首单时间。</p><p><code>如果知道查询结果只有一条或者只要最大/最小一条记录，建议用limit 1</code></p><ul><li>加上limit 1后,只要找到了对应的一条记录,就不会继续向下扫描了,效率将会大大提高。</li><li>当然，如果name是唯一索引的话，是不必要加上limit 1了，因为limit的存在主要就是为了防止全表扫描，从而提高性能,如果一个语句本身可以预知不用全表扫描，有没有limit ，性能的差别并不大。</li></ul><p><strong>反例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id, create_date </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> create_date </span><span style="color:#F78C6C;">asc</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>根据用户id查询订单，按下单时间排序，先查出该用户所有的订单数据，得到一个订单集合。然后在代码中，获取第一个元素的数据，即首单的数据，就能获取首单时间。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">Order</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> list </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> orderMapper.getOrderList</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">Order order </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> list.get(</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>虽说这种做法在功能上没有问题，但它的效率非常不高，需要先查询出所有的数据，有点浪费资源。</p><p>那么，如何优化呢？</p><p><strong>正例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id, create_date </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> create_date </span><span style="color:#F78C6C;">asc</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>使用<code>limit 1</code>，只返回该用户下单时间最小的那一条数据即可。</p><blockquote><p>此外，在删除或者修改数据时，为了防止误操作，导致删除或修改了不相干的数据，也可以在sql语句最后加上limit。</p></blockquote><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">,edit_time</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&gt;=</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F78C6C;">200</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>这样即使误操作，比如把id搞错了，也不会对太多的数据造成影响。</p><h2 id="_6-in中值太多" tabindex="-1">6 in中值太多 <a class="header-anchor" href="#_6-in中值太多" aria-label="Permalink to &quot;6 in中值太多&quot;">​</a></h2><p>对于批量查询接口，我们通常会使用<code>in</code>关键字过滤出数据。比如：想通过指定的一些id，批量查询出用户信息。</p><p>sql语句如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> category</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">...</span><span style="color:#F78C6C;">100000000</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>如果我们不做任何限制，该查询语句一次性可能会查询出非常多的数据，很容易导致接口超时。</p><p>这时该怎么办呢？</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> category</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">...</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">500</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>可以在sql中对数据用limit做限制。</p><p>不过我们更多的是要在业务代码中加限制，伪代码如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">public List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">Category</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> getCategory(List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">Long</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> ids) {</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F78C6C;">if</span><span style="color:#A6ACCD;">(CollectionUtils.isEmpty(ids)) {</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#F78C6C;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">null</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   }</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F78C6C;">if</span><span style="color:#A6ACCD;">(ids.size</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">500</span><span style="color:#A6ACCD;">) {</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#F78C6C;">throw</span><span style="color:#A6ACCD;"> new BusinessException(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">一次最多允许查询500条记录</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">   }</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F78C6C;">return</span><span style="color:#A6ACCD;"> mapper.getCategoryList(ids);</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>还有一个方案就是：如果ids超过500条记录，可以分批用多线程去查询数据。每批只查500条记录，最后把查询到的数据汇总到一起返回。</p><p>不过这只是一个临时方案，不适合于ids实在太多的场景。因为ids太多，即使能快速查出数据，但如果返回的数据量太大了，网络传输也是非常消耗性能的，接口性能始终好不到哪里去。</p><h2 id="_7-增量查询" tabindex="-1">7 增量查询 <a class="header-anchor" href="#_7-增量查询" aria-label="Permalink to &quot;7 增量查询&quot;">​</a></h2><p>有时候，我们需要通过远程接口查询数据，然后同步到另外一个数据库。</p><p><strong>反例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user;</span></span></code></pre></div><p>如果直接获取所有的数据，然后同步过去。这样虽说非常方便，但是带来了一个非常大的问题，就是如果数据很多的话，查询性能会非常差。</p><p>这时该怎么办呢？</p><p><strong>正例：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">#{lastId} </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> create_time </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> #{lastCreateTime} </span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>按id和时间升序，每次只同步一批数据，这一批数据只有100条记录。每次同步完成之后，保存这100条数据中最大的id和时间，给同步下一批数据的时候用。</p><p>通过这种增量查询的方式，能够提升单次查询的效率。</p><h2 id="_8-高效的分页" tabindex="-1">8 高效的分页 <a class="header-anchor" href="#_8-高效的分页" aria-label="Permalink to &quot;8 高效的分页&quot;">​</a></h2><p>有时候，列表页在查询数据时，为了避免一次性返回过多的数据影响接口性能，我们一般会对查询接口做分页处理。</p><p>在mysql中分页一般用的<code>limit</code>关键字：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>如果表中数据量少，用limit关键字做分页，没啥问题。但如果表中数据量很多，用它就会出现性能问题。</p><p>比如现在分页参数变成了：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1000000</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>mysql会查到1000020条数据，然后丢弃前面的1000000条，只查后面的20条数据，这个是非常浪费资源的。</p><p>那么，这种海量数据该怎么分页呢？</p><p>优化sql：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 方案一 ：返回上次查询的最大记录(偏移量)</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1000000</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 方案二：order by + 索引</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id，</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> employee </span></span>
<span class="line"><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> id  </span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10000</span><span style="color:#A6ACCD;">，</span><span style="color:#F78C6C;">10</span></span></code></pre></div><p>先找到上次分页最大的id，然后利用id上的索引查询。不过该方案，要求id是连续的，并且有序的。</p><p>还能使用<code>between</code>优化分页。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">between</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1000000</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1000020</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>需要注意的是between要在唯一索引上分页，不然会出现每页大小不一致的问题。</p><p>理由：</p><ul><li>当偏移量最大的时候，查询效率就会越低，因为Mysql并非是跳过偏移量直接去取后面的数据，而是先把偏移量+要取的条数，然后再把前面偏移量这一段的数据抛弃掉再返回的。</li><li>如果使用优化方案一，返回上次最大查询记录（偏移量），这样可以跳过偏移量，效率提升不少。</li><li>方案二使用order by+索引，也是可以提高查询效率的。</li></ul><h2 id="_9-用连接查询代替子查询" tabindex="-1">9 用连接查询代替子查询 <a class="header-anchor" href="#_9-用连接查询代替子查询" aria-label="Permalink to &quot;9 用连接查询代替子查询&quot;">​</a></h2><p>mysql中如果需要<code>从两张以上的表中查询出数据的话</code>，一般有两种实现方式：<code>子查询</code> 和 <code>连接查询</code>。</p><p>子查询的例子如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>子查询语句可以通过<code>in</code>关键字实现，一个查询语句的条件落在另一个select语句的查询结果中。程序先运行在嵌套在最内层的语句，再运行外层的语句。</p><p>子查询语句的优点是简单，结构化，如果涉及的表数量不多的话。</p><p>但缺点是mysql执行子查询时，需要创建临时表，查询完毕后，需要再删除这些临时表，有一些额外的性能消耗。</p><p>这时可以改成连接查询。具体例子如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select o</span><span style="color:#89DDFF;">.*</span><span style="color:#A6ACCD;"> from order o</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join user u on o</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> u</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span></span>
<span class="line"><span style="color:#A6ACCD;">where u</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span></span></code></pre></div><h2 id="_10-join的表不宜过多" tabindex="-1">10 join的表不宜过多 <a class="header-anchor" href="#_10-join的表不宜过多" aria-label="Permalink to &quot;10 join的表不宜过多&quot;">​</a></h2><p>根据阿里巴巴开发者手册的规定，join表的数量不应该超过<code>3</code>个。</p><ul><li>连表越多，编译的时间和开销也就越大。</li><li>把连接表拆开成较小的几个执行，可读性更高。</li><li>如果一定需要连接很多表才能得到数据，那么意味着糟糕的设计了。</li></ul><p><strong>反例：</strong></p><div class="language-sqlite"><button title="Copy Code" class="copy"></button><span class="lang">sqlite</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select a.name,b.name.c.name,d.name</span></span>
<span class="line"><span style="color:#A6ACCD;">from a </span></span>
<span class="line"><span style="color:#A6ACCD;">inner join b on a.id = b.a_id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join c on c.b_id = b.id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join d on d.c_id = c.id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join e on e.d_id = d.id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join f on f.e_id = e.id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join g on g.f_id = f.id</span></span></code></pre></div><p>如果join太多，mysql在选择索引的时候会非常复杂，很容易选错索引。</p><p>并且如果没有命中中，nested loop join 就是分别从两个表读一行数据进行两两对比，复杂度是 n^2。</p><p>所以我们应该尽量控制join表的数量。</p><p><strong>正例：</strong></p><div class="language-sqlite"><button title="Copy Code" class="copy"></button><span class="lang">sqlite</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select a.name,b.name.c.name,a.d_name </span></span>
<span class="line"><span style="color:#A6ACCD;">from a </span></span>
<span class="line"><span style="color:#A6ACCD;">inner join b on a.id = b.a_id</span></span>
<span class="line"><span style="color:#A6ACCD;">inner join c on c.b_id = b.id</span></span></code></pre></div><p>如果实现业务场景中需要查询出另外几张表中的数据，可以在a、b、c表中<code>冗余专门的字段</code>，比如：在表a中冗余d_name字段，保存需要查询出的数据。</p><p>不过我之前也见过有些ERP系统，并发量不大，但业务比较复杂，需要join十几张表才能查询出数据。</p><p>所以join表的数量要根据系统的实际情况决定，不能一概而论，尽量越少越好。</p><p><code>当在SQL语句中连接多个表时,请使用表的别名，并把别名前缀于每一列上，这样语义更加清晰</code>。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> A innerjoin B </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> A.deptId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> B.deptId;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;">  memeber.name,deptment.deptName </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> A member innerjoin B deptment </span></span>
<span class="line"><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> member.deptId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> deptment.deptId;</span></span></code></pre></div><h2 id="_11-join时要注意" tabindex="-1">11 join时要注意 <a class="header-anchor" href="#_11-join时要注意" aria-label="Permalink to &quot;11 join时要注意&quot;">​</a></h2><p>我们在涉及到多张表联合查询的时候，一般会使用<code>join</code>关键字。</p><p>Inner join 、left join、right join，优<code>先使用Inner join</code>，如果是left join，左边表结果尽量小</p><blockquote><ul><li>Inner join 内连接，在两张表进行连接查询时，只保留两张表中完全匹配的结果集</li><li>left join 在两张表进行连接查询时，会返回左表所有的行，即使在右表中没有匹配的记录。</li><li>right join 在两张表进行连接查询时，会返回右表所有的行，即使在左表中没有匹配的记录。</li></ul></blockquote><p>都满足SQL需求的前提下，推荐优先使用Inner join（内连接），如果要使用left join，左边表数据结果尽量小，如果有条件的尽量放到左边处理。</p><p>而join使用最多的是left join和inner join。</p><ul><li><code>left join</code>：求两个表的交集外加左表剩下的数据。</li><li><code>inner join</code>：求两个表交集的数据。</li></ul><p>使用inner join的示例如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> o.id,o.code,u.name </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order o </span></span>
<span class="line"><span style="color:#F78C6C;">inner join</span><span style="color:#A6ACCD;"> user u </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> o.user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> u.id</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> u.status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>如果两张表使用inner join关联，mysql会自动选择两张表中的小表，去驱动大表，所以性能上不会有太大的问题。</p><p>使用left join的示例如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> o.id,o.code,u.name </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order o </span></span>
<span class="line"><span style="color:#F78C6C;">left join</span><span style="color:#A6ACCD;"> user u </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> o.user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> u.id</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> u.status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>如果两张表使用left join关联，mysql会默认用left join关键字左边的表，去驱动它右边的表。如果左边的表数据很多时，就会出现性能问题。</p><blockquote><p>要特别注意的是在用left join关联查询时，左边要用小表，右边可以用大表。如果能用inner join的地方，尽量少用left join。</p></blockquote><h2 id="_12-控制索引的数量" tabindex="-1">12 控制索引的数量 <a class="header-anchor" href="#_12-控制索引的数量" aria-label="Permalink to &quot;12 控制索引的数量&quot;">​</a></h2><p>众所周知，<code>索引能够显著的提升查询sql的性能，但索引数量并非越多越好</code>。</p><p>因为表中新增数据时，需要同时为它创建索引，<code>而索引是需要额外的存储空间的，而且还会有一定的性能消耗</code>。</p><p>阿里巴巴的开发者手册中规定，单表的索引数量应该尽量控制在<code>5</code>个以内，并且单个索引中的字段数不超过<code>5</code>个。</p><p>mysql使用的B+树的结构来保存索引的，在insert、update和delete操作时，需要更新B+树索引。如果索引过多，会消耗很多额外的性能。</p><p>那么，问题来了，如果表中的索引太多，超过了5个该怎么办？</p><p>这个问题要辩证的看，<code>如果你的系统并发量不高，表中的数据量也不多，其实超过5个也可以，只要不要超过太多就行</code>。</p><p>但对于一些高并发的系统，请务必遵守单表索引数量不要超过5的限制。</p><p>那么，高并发系统如何优化索引数量？</p><p><code>能够建联合索引，就别建单个索引，可以删除无用的单个索引</code>。</p><p>将部分查询功能迁移到其他类型的数据库中，比如：Elastic Seach、HBase等，在业务表中只需要建几个关键索引即可。</p><h2 id="_13-选择合理的字段类型" tabindex="-1">13 选择合理的字段类型 <a class="header-anchor" href="#_13-选择合理的字段类型" aria-label="Permalink to &quot;13 选择合理的字段类型&quot;">​</a></h2><p><code>char</code>表示固定字符串类型，该类型的字段存储空间的固定的，<code>会浪费存储空间</code>。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> column code </span><span style="color:#C792EA;">char</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><code>varchar</code>表示变长字符串类型，该类型的字段存储空间会根据实际数据的长度调整，<code>不会浪费存储空间</code>。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> column code </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>如果是长度固定的字段，比如用户手机号，一般都是11位的，可以定义成char类型，长度是11字节。</p><p>但如果是企业名称字段，假如定义成char类型，就有问题了。</p><p>如果长度定义得太长，比如定义成了200字节，而实际企业长度只有50字节，则会浪费150字节的存储空间。</p><p>如果长度定义得太短，比如定义成了50字节，但实际企业名称有100字节，就会存储不下，而抛出异常。</p><p>所以建议将企业名称改成varchar类型，变长字段存储空间小，可以节省存储空间，而且对于查询来说，在一个相对较小的字段内搜索效率显然要高些。</p><p>我们在选择字段类型时，应该遵循这样的原则：</p><ol><li><code>能用数字类型，就不用字符串</code>，因为字符的处理往往比数字要慢。</li><li><code>尽可能使用小的类型</code>，比如：用bit存布尔值，用tinyint存枚举值等。</li><li><code>长度固定的字符串字段，用char类型</code>。</li><li><code>长度可变的字符串字段，用varchar类型</code>。</li><li><code>金额字段用decimal，避免精度丢失问题</code>。</li></ol><p>还有很多原则，这里就不一一列举了。</p><h2 id="_14-提升group-by的效率" tabindex="-1">14 提升group by的效率 <a class="header-anchor" href="#_14-提升group-by的效率" aria-label="Permalink to &quot;14 提升group by的效率&quot;">​</a></h2><p>我们有很多业务场景需要使用<code>group by</code>关键字，它主要的功能是去重和分组。</p><p>通常它会跟<code>having</code>一起配合使用，表示分组后再根据一定的条件过滤数据。</p><p><strong>反例：先分组，再过滤</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> user_id,user_name </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order</span></span>
<span class="line"><span style="color:#F78C6C;">group by</span><span style="color:#A6ACCD;"> user_id</span></span>
<span class="line"><span style="color:#F78C6C;">having</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#89DDFF;">&lt;=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">200</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>这种写法性能不好，它先把所有的订单根据用户id分组之后，再去过滤用户id大于等于200的用户。</p><p>分组是一个相对耗时的操作，为什么我们不先缩小数据的范围之后，再分组呢？</p><p><strong>正例：先过滤，后分组</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> user_id,user_name </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#89DDFF;">&lt;=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">200</span></span>
<span class="line"><span style="color:#F78C6C;">group by</span><span style="color:#A6ACCD;"> user_id</span></span></code></pre></div><p>理由：使用where条件在分组前，就把多余的数据过滤掉了，这样分组时效率就会更高一些。</p><blockquote><p>其实这是一种思路，不仅限于group by的优化。我们的sql语句在做一些耗时的操作之前，应尽可能缩小数据范围，这样能提升sql整体的性能。</p></blockquote><h2 id="_15-索引优化" tabindex="-1">15 索引优化 <a class="header-anchor" href="#_15-索引优化" aria-label="Permalink to &quot;15 索引优化&quot;">​</a></h2><p>sql优化当中，有一个非常重要的内容就是：<code>索引优化</code>。</p><p><code>索引不宜太多，一般5个以内</code></p><ul><li>索引并不是越多越好，索引虽然提高了查询的效率，但是也降低了插入和更新的效率。</li><li>insert或update时有可能会重建索引，所以建索引需要慎重考虑，视具体情况来定。</li><li>一个表的索引数最好不要超过5个，若太多需要考虑一些索引是否没有存在的必要。</li></ul><p>很多时候sql语句，走了索引，和没有走索引，执行效率差别很大。所以索引优化被作为sql优化的首选。</p><p>日常开发写SQL的时候，尽量养成一个习惯吧。<code>用explain分析一下你写的SQL，尤其是走不走索引这一块</code>。</p><p>索引优化的第一步是：检查sql语句有没有走索引。</p><p>那么，如何查看sql走了索引没？</p><p>可以使用<code>explain</code>命令，查看mysql的执行计划。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> code</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">002</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>结果：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5.13/202205200954607.png" alt="image-20220520095423509" style="zoom:80%;"><p>如果字段类型是字符串，where时一定用引号括起来，否则索引失效</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">123</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>理由：</p><ul><li>为什么第一条语句未加单引号就不走索引了呢？这是因为不加单引号时，是字符串跟数字的比较，它们类型不匹配，MySQL会做隐式的类型转换，把它们转换为浮点数再做比较。</li></ul><p><code>使用联合索引时，注意索引列的顺序，一般遵循最左匹配原则</code></p><p>表结构：（有一个联合索引idxuseridage，userId在前，age在后）</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_userid_age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 符合最左匹配原则</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">；</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 符合最左匹配原则</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>理由：</p><ul><li>当我们创建一个联合索引的时候，如(k1,k2,k3)，相当于创建了（k1）、(k1,k2)和(k1,k2,k3)三个索引，这就是最左匹配原则。</li><li>联合索引不满足最左原则，索引一般会失效，但是这个还跟Mysql优化器有关的。</li></ul><p><code>对查询进行优化，应考虑在where及order by涉及的列上建立索引，尽量避免全表扫描</code></p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">address</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">深圳</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> age ;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 添加索引</span></span>
<span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">index</span><span style="color:#A6ACCD;"> idx_address_age (</span><span style="color:#F78C6C;">address</span><span style="color:#A6ACCD;">,age)</span></span></code></pre></div><p><code>在适当的时候，使用覆盖索引</code></p><p>覆盖索引能够使得你的SQL语句不需要回表，仅仅访问索引就能够得到所有需要的数据，大大提高了查询效率。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- like模糊查询，不走索引了</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%123%</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- id为主键，那么为普通索引，即覆盖索引登场了</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%123%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><code>索引不适合建在有大量重复数据的字段上，如性别这类型数据库字段</code></p><p>因为SQL优化器是根据表中数据量来进行查询优化的，如果索引列有大量重复数据，Mysql查询优化器推算发现不走索引的成本更低，很可能就放弃索引了。</p><p>通过这几列可以判断索引使用情况，执行计划包含列的含义如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022/202204211142914.png" alt="image-20220421114259809" style="zoom:60%;"><p>说实话，sql语句没有走索引，排除没有建索引之外，最大的可能性是索引失效了。</p><p>下面说说索引失效的常见原因：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022/202204211143266.png" alt="image-20220421114356191" style="zoom:67%;"><p>如果不是上面的这些原因，则需要再进一步排查一下其他原因。</p><p>此外，你有没有遇到过这样一种情况：明明是同一条sql，只有入参不同而已。有的时候走的索引a，有的时候却走的索引b？</p><p>没错，有时候mysql会选错索引。</p><p>必要时可以使用<code>force index</code>来强制查询sql走某个索引。</p><p>至于为什么mysql会选错索引，后面有专门的文章介绍的，这里先留点悬念。</p><h2 id="_16-where使用事项⭐" tabindex="-1">16 where使用事项⭐ <a class="header-anchor" href="#_16-where使用事项⭐" aria-label="Permalink to &quot;16 where使用事项⭐&quot;">​</a></h2><p>新建一个user表，它有一个普通索引userId，表结构如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,  </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><h3 id="_1-避免使用-or" tabindex="-1">1 避免使用 or <a class="header-anchor" href="#_1-避免使用-or" aria-label="Permalink to &quot;1 避免使用 or&quot;">​</a></h3><p>假设现在需要查询userid为1或者年龄为18岁的用户，很容易有以下SQL</p><p>反例:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">or</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">18</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 使用union all</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">union all</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">18</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 或者分开两条sql写：</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">18</span></span></code></pre></div><p>理由：使用or可能会使索引失效，从而全表扫描。</p><blockquote><p>对于or+没有索引的age这种情况，假设它走了userId的索引，但是走到age查询条件时，它还得全表扫描，也就是需要三步过程：全表扫描+索引扫描+合并 如果它一开始就走全表扫描，直接一遍扫描就完事。mysql是有优化器的，处于效率与成本考虑，遇到or条件，索引可能失效，看起来也合情合理。</p></blockquote><h3 id="_2-避免返回多余的行" tabindex="-1">2 避免返回多余的行 <a class="header-anchor" href="#_2-避免返回多余的行" aria-label="Permalink to &quot;2 避免返回多余的行&quot;">​</a></h3><p>使用where条件限定要查询的数据，避免返回多余的行</p><p>假设业务场景是这样：查询某个用户是否是会员。曾经看过老的实现代码是这样。。。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> isVip</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userId</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> isVip</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">1</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>理由：需要什么数据，就去查什么数据，避免返回不必要的数据，节省开销。</p><h3 id="_3-避免表达式操作" tabindex="-1">3 避免表达式操作 <a class="header-anchor" href="#_3-避免表达式操作" aria-label="Permalink to &quot;3 避免表达式操作&quot;">​</a></h3><p>应尽量避免在where子句中对字段进行表达式操作，这将导致系统放弃使用索引而进行全表扫描</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">；</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">；</span></span></code></pre></div><p>理由：虽然age加了索引，但是因为对它进行运算，索引直接迷路了。。。</p><h3 id="_4-避免使用-或-操作符" tabindex="-1">4 避免使用!=或&lt;&gt;操作符 <a class="header-anchor" href="#_4-避免使用-或-操作符" aria-label="Permalink to &quot;4 避免使用!=或&lt;&gt;操作符&quot;">​</a></h3><p>应尽量避免在where子句中使用!=或&lt;&gt;操作符，否则将引擎放弃使用索引而进行全表扫描。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> age,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">&lt;&gt;</span><span style="color:#F78C6C;">18</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">可以考虑分开两条sql写</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> age,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F78C6C;">18</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> age,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">&gt;</span><span style="color:#F78C6C;">18</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>理由：使用!=和&lt;&gt;很可能会让索引失效</p><h3 id="_5-考虑使用默认值代替null" tabindex="-1">5 考虑使用默认值代替null <a class="header-anchor" href="#_5-考虑使用默认值代替null" aria-label="Permalink to &quot;5 考虑使用默认值代替null&quot;">​</a></h3><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#F78C6C;">is not null</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 设置0为默认值</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">&gt;</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>理由：并不是说使用了is null 或者 is not null 就会不走索引了，这个跟mysql版本以及查询成本都有关。</p><blockquote><p>如果mysql优化器发现，走索引比不走索引成本还要高，肯定会放弃索引，这些条件 <code>！=，&gt;isnull，isnotnull</code>经常被认为让索引失效，其实是因为一般情况下，查询的成本高，优化器自动放弃索引的。</p><p>如果把null值，换成默认值，很多时候让走索引成为可能，同时，表达意思会相对清晰一点。</p></blockquote><h2 id="_17-优化你的like语句" tabindex="-1">17 优化你的like语句 <a class="header-anchor" href="#_17-优化你的like语句" aria-label="Permalink to &quot;17 优化你的like语句&quot;">​</a></h2><p>日常开发中，如果用到模糊关键字查询，很容易想到like，但是like很可能让你的索引失效。</p><h3 id="_1、反例" tabindex="-1">1、反例 <a class="header-anchor" href="#_1、反例" aria-label="Permalink to &quot;1、反例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> citys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%大连</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> (不使用索引)</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> citys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%大连%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> (不使用索引)</span></span></code></pre></div><h3 id="_2、正例" tabindex="-1">2、正例 <a class="header-anchor" href="#_2、正例" aria-label="Permalink to &quot;2、正例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> citys </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">大连%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> (使用索引) 。</span></span></code></pre></div><h3 id="_3、理由" tabindex="-1">3、理由 <a class="header-anchor" href="#_3、理由" aria-label="Permalink to &quot;3、理由&quot;">​</a></h3><ul><li>首先尽量避免模糊查询，如果必须使用，不采用全模糊查询，也应尽量采用右模糊查询， 即<code>like ‘…%’</code>，是会使用索引的；</li><li>左模糊<code>like ‘%...’</code>无法直接使用索引，但可以利用<code>reverse + function index</code>的形式，变化成 <code>like ‘…%’</code>；</li><li>全模糊查询是无法优化的，一定要使用的话建议使用搜索引擎。</li></ul><h2 id="_18-避免在索引列上使用内置函数" tabindex="-1">18 避免在索引列上使用内置函数 <a class="header-anchor" href="#_18-避免在索引列上使用内置函数" aria-label="Permalink to &quot;18 避免在索引列上使用内置函数&quot;">​</a></h2><p>业务需求：查询最近七天内登陆过的用户(假设loginTime加了索引)</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId,loginTime </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> loginuser </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> Date_ADD(loginTime,Interval </span><span style="color:#F78C6C;">7</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">DAY</span><span style="color:#A6ACCD;">) </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain  </span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId,loginTime </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> loginuser </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;">  loginTime </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> Date_ADD(</span><span style="color:#F78C6C;">NOW</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,INTERVAL </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">7</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">DAY</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>理由：</p><ul><li>索引列上使用mysql的内置函数，索引失效</li><li>如果索引列不加内置函数，索引还是会走的。</li></ul><h2 id="_19-慎用distinct关键字" tabindex="-1">19 慎用distinct关键字 <a class="header-anchor" href="#_19-慎用distinct关键字" aria-label="Permalink to &quot;19 慎用distinct关键字&quot;">​</a></h2><p>distinct 关键字一般用来过滤重复记录，以返回不重复的记录。在查询一个字段或者很少字段的情况下使用时，给查询带来优化效果。但是在字段很多的时候使用，却会大大降低查询效率。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT DISTINCT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;">  user;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select DISTINCT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user;</span></span></code></pre></div><p>理由：</p><ul><li>带distinct的语句cpu时间和占用时间都高于不带distinct的语句。因为当查询很多字段时，如果使用distinct，数据库引擎就会对数据进行比较，过滤掉重复数据，然而这个比较、过滤的过程会占用系统资源，cpu时间。</li></ul><h2 id="_20-删除冗余和重复索引" tabindex="-1">20 删除冗余和重复索引 <a class="header-anchor" href="#_20-删除冗余和重复索引" aria-label="Permalink to &quot;20 删除冗余和重复索引&quot;">​</a></h2><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)    </span></span>
<span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_userId_age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>正例:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 删除userId索引，因为组合索引（A，B）相当于创建了（A）和（A，B）索引  </span></span>
<span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_userId_age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>理由：</p><ul><li>重复的索引需要维护，并且优化器在优化查询的时候也需要逐个地进行考虑，这会影响性能的。</li></ul><h2 id="_21-删改使用limit或分批次" tabindex="-1">21. 删改使用limit或分批次 <a class="header-anchor" href="#_21-删改使用limit或分批次" aria-label="Permalink to &quot;21. 删改使用limit或分批次&quot;">​</a></h2><p>避免同时修改或删除过多数据，因为会造成cpu利用率过高，从而影响别人对数据库的访问。</p><h3 id="反例" tabindex="-1">反例 <a class="header-anchor" href="#反例" aria-label="Permalink to &quot;反例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 一次删除10万或者100万+？</span></span>
<span class="line"><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F78C6C;">100000</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 或者采用单一循环操作，效率低，时间漫长</span></span>
<span class="line"><span style="color:#A6ACCD;">for（User user：list）{   </span></span>
<span class="line"><span style="color:#A6ACCD;">	</span><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user；</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><h3 id="正例" tabindex="-1">正例 <a class="header-anchor" href="#正例" aria-label="Permalink to &quot;正例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 分批进行删除,如每次500</span></span>
<span class="line"><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F78C6C;">500</span></span>
<span class="line"><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> product </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&gt;=</span><span style="color:#F78C6C;">500</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">&lt;</span><span style="color:#F78C6C;">1000</span><span style="color:#A6ACCD;">；</span></span></code></pre></div><h3 id="理由" tabindex="-1">理由 <a class="header-anchor" href="#理由" aria-label="Permalink to &quot;理由&quot;">​</a></h3><h4 id="_1、降低写错sql的代价" tabindex="-1">1、降低写错SQL的代价 <a class="header-anchor" href="#_1、降低写错sql的代价" aria-label="Permalink to &quot;1、降低写错SQL的代价&quot;">​</a></h4><p>清空表数据可不是小事情，一个手抖全没了，删库跑路？如果加limit，删错也只是丢失部分数据，可以通过binlog日志快速恢复的。</p><h4 id="_2、sql效率很可能更高" tabindex="-1">2、SQL效率很可能更高 <a class="header-anchor" href="#_2、sql效率很可能更高" aria-label="Permalink to &quot;2、SQL效率很可能更高&quot;">​</a></h4><p>SQL中加了<code>limit 1</code>，如果第一条就命中目标<code>return</code>， 没有<code>limit</code>的话，还会继续执行扫描表。</p><h4 id="_3、避免长事务" tabindex="-1">3、避免长事务 <a class="header-anchor" href="#_3、避免长事务" aria-label="Permalink to &quot;3、避免长事务&quot;">​</a></h4><p><code>delete</code>执行时,如果<code>age</code>加了索引，MySQL会将所有相关的行加写锁和间隙锁，所有执行相关行会被锁住，如果删除数量大，会直接影响相关业务无法使用。</p><h4 id="_4、数据量大的话-容易把cpu打满" tabindex="-1">4、数据量大的话，容易把CPU打满 <a class="header-anchor" href="#_4、数据量大的话-容易把cpu打满" aria-label="Permalink to &quot;4、数据量大的话，容易把CPU打满&quot;">​</a></h4><p>如果你删除数据量很大时，不加 limit限制一下记录数，容易把<code>cpu</code>打满，导致越删越慢。</p><h4 id="_5、锁表" tabindex="-1">5、锁表 <a class="header-anchor" href="#_5、锁表" aria-label="Permalink to &quot;5、锁表&quot;">​</a></h4><p>一次性删除太多数据，可能造成锁表，会有lock wait timeout exceed的错误，所以建议分批操作。</p><h2 id="_22-exist-in的合理利用" tabindex="-1">22 exist&amp;in的合理利用 <a class="header-anchor" href="#_22-exist-in的合理利用" aria-label="Permalink to &quot;22 exist&amp;in的合理利用&quot;">​</a></h2><p>假设表A表示某企业的员工表，表B表示部门表，查询所有部门的所有员工，很容易有以下SQL:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> A </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> deptId </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> deptId </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> B);</span></span></code></pre></div><p>这样写等价于：</p><blockquote><p>先查询部门表B</p><p>select deptId from B</p><p>再由部门deptId，查询A的员工</p><p>select * from A where A.deptId = B.deptId</p></blockquote><p>可以抽象成这样的一个循环：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">&lt;&gt;</span><span style="color:#A6ACCD;"> resultSet;    </span></span>
<span class="line"><span style="color:#A6ACCD;">for(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;i</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">B.length;i</span><span style="color:#89DDFF;">++</span><span style="color:#A6ACCD;">) {          </span></span>
<span class="line"><span style="color:#A6ACCD;">	for(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> j</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;j</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">A.length;j</span><span style="color:#89DDFF;">++</span><span style="color:#A6ACCD;">) {          </span></span>
<span class="line"><span style="color:#A6ACCD;">		</span><span style="color:#F78C6C;">if</span><span style="color:#A6ACCD;">(A[i].id</span><span style="color:#89DDFF;">==</span><span style="color:#A6ACCD;">B[j].id) {             </span></span>
<span class="line"><span style="color:#A6ACCD;">			resultSet.add(A[i]);             </span></span>
<span class="line"><span style="color:#A6ACCD;">			</span><span style="color:#F78C6C;">break</span><span style="color:#A6ACCD;">;          </span></span>
<span class="line"><span style="color:#A6ACCD;">		}       </span></span>
<span class="line"><span style="color:#A6ACCD;">	}    </span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>显然，除了使用in，我们也可以用exists实现一样的查询功能，如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> A </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">exists</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> B </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> A.deptId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> B.deptId);</span></span></code></pre></div><p>因为exists查询的理解就是，先执行主查询，获得数据后，再放到子查询中做条件验证，根据验证结果（true或者false），来决定主查询的数据结果是否得意保留。</p><p>那么，这样写就等价于：</p><blockquote><p>select * from A,先从A表做循环</p><p>select * from B where A.deptId = B.deptId,再从B表做循环.</p></blockquote><p>同理，可以抽象成这样一个循环：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">&lt;&gt;</span><span style="color:#A6ACCD;"> resultSet ;</span></span>
<span class="line"><span style="color:#A6ACCD;">for(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> i</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;i</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">A.length;i</span><span style="color:#89DDFF;">++</span><span style="color:#A6ACCD;">) {</span></span>
<span class="line"><span style="color:#A6ACCD;">	for(</span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> j</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;j</span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;">B.length;j</span><span style="color:#89DDFF;">++</span><span style="color:#A6ACCD;">) {          </span></span>
<span class="line"><span style="color:#A6ACCD;">		</span><span style="color:#F78C6C;">if</span><span style="color:#A6ACCD;">(A[i].deptId</span><span style="color:#89DDFF;">==</span><span style="color:#A6ACCD;">B[j].deptId) {             </span></span>
<span class="line"><span style="color:#A6ACCD;">			resultSet.add(A[i]);            </span></span>
<span class="line"><span style="color:#A6ACCD;">			</span><span style="color:#F78C6C;">break</span><span style="color:#A6ACCD;">;        </span></span>
<span class="line"><span style="color:#A6ACCD;">		}      </span></span>
<span class="line"><span style="color:#A6ACCD;">	}   </span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>数据库最费劲的就是跟程序链接释放。假设链接了两次，每次做上百万次的数据集查询，查完就走，这样就只做了两次；相反建立了上百万次链接，申请链接释放反复重复，这样系统就受不了了。即mysql优化原则，就是小表驱动大表，小的数据集驱动大的数据集，从而让性能更优。</p><p>因此，我们要选择最外层循环小的，也就是，如果<strong>B的数据量小于A，适合使用in，如果B的数据量大于A，即适合选择exist</strong>。</p><h2 id="_23-尽量使用数字型字段" tabindex="-1">23 尽量使用数字型字段 <a class="header-anchor" href="#_23-尽量使用数字型字段" aria-label="Permalink to &quot;23 尽量使用数字型字段&quot;">​</a></h2><p>尽量使用数字型字段，若只含数值信息的字段尽量不要设计为字符型</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">king_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">（</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">） </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">守护者Id</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p>正例：</p><ol><li>主键（id）：<code>primary key</code>优先使用数值类型<code>int</code>，<code>tinyint</code></li><li>性别（sex）：0代表女，1代表男；数据库没有布尔类型，<code>mysql</code>推荐使用<code>tinyint</code></li></ol><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">king_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">守护者Id</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><ol><li>因为引擎在处理查询和连接时会逐个比较字符串中每一个字符；</li><li>而对于数字型而言只需要比较一次就够了；</li><li>字符会降低查询和连接的性能，并会增加存储开销；</li><li>相对于数字型字段，字符型会降低查询和连接的性能，并会增加存储开销。</li></ol><h2 id="_24-避免向客户端返回过多数据" tabindex="-1">24 避免向客户端返回过多数据 <a class="header-anchor" href="#_24-避免向客户端返回过多数据" aria-label="Permalink to &quot;24 避免向客户端返回过多数据&quot;">​</a></h2><p>假设业务需求是，用户请求查看自己最近一年观看过的直播数据。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 一次性查询所有数据回来</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> LivingInfo </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> watchId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">useId </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> watchTime </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> Date_sub(</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,Interval </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> Y)</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 分页查询</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> LivingInfo </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> watchId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> useId </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> watchTime</span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> Date_sub(</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,Interval </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> Y) </span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> offset，pageSize</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 如果是前端分页，可以先查询前两百条记录，因为一般用户应该也不会往下翻太多页，</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> LivingInfo </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> watchId </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">useId </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> watchTime</span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> Date_sub(</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,Interval </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> Y) </span></span>
<span class="line"><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">200</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h2 id="_25-update可能出现bug⭐" tabindex="-1">25. update可能出现bug⭐ <a class="header-anchor" href="#_25-update可能出现bug⭐" aria-label="Permalink to &quot;25. update可能出现bug⭐&quot;">​</a></h2><blockquote><p>结论：不要使用update语句的影响行数做重要的业务判断!</p></blockquote><p>业务系统中，使用update语句更新数据是再正常不过的场景，我们也经常通过update更新的行数，来做一些业务判断，类似下面的伪代码：(mybatis + mysql 场景)</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">xxxMapper</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">updateByPrimaryKeySelective</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">entity</span><span style="color:#89DDFF;">)&gt;</span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">){</span></span>
<span class="line"><span style="color:#89DDFF;">   </span><span style="color:#676E95;font-style:italic;">//更新成功，做其它业务处理</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>但是这里有一个坑，mysql中update影响行数&gt;0是有条件的</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 第一次执行：Affected rows: 1</span></span>
<span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> course </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">java</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 再次执行：Affected rows: 0</span></span>
<span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> course </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">java</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>也就是说，<strong>当待更新的记录与原始记录旧值相同时，mysql其实并不会做任何更新。</strong> 换言之，如果上游传过来的数据，与数据库本身的旧值相等，没有变化时，update语句影响行数为0。这与另一种场景：&quot;更新一条并不存在的记录，影响行数返回0&quot; 无法区分。</p><h2 id="_26-选择表合适存储引擎" tabindex="-1">26. 选择表合适存储引擎 <a class="header-anchor" href="#_26-选择表合适存储引擎" aria-label="Permalink to &quot;26. 选择表合适存储引擎&quot;">​</a></h2><ul><li>myisam：应用时以读和插入操作为主，只有少量的更新和删除，并且对事务的完整性，并发性要求不是很高的。</li><li>InnoDB：事务处理，以及并发条件下要求数据的一致性。除了插入和查询外，包括很多的更新和删除。（InnoDB 有效地降低删除和更新导致的锁定）。对于支持事务的 InnoDB类 型的表来说，影响速度的主要原因是 AUTOCOMMIT 默认设置是打开的，而且程序没有显式调用 BEGIN 开始事务，导致每插入一条都自动提交，严重影响了速度。可以在执行 SQL 前调用 begin，多条 SQL 形成一个事物（即使 autocommit 打开也可以），将大大提高性能。</li></ul><h2 id="_27-使用varchar代替char" tabindex="-1">27. 使用varchar代替char <a class="header-anchor" href="#_27-使用varchar代替char" aria-label="Permalink to &quot;27. 使用varchar代替char&quot;">​</a></h2><h3 id="_1、反例-1" tabindex="-1">1、反例 <a class="header-anchor" href="#_1、反例-1" aria-label="Permalink to &quot;1、反例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">address</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">char</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">地址</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><h3 id="_2、正例-1" tabindex="-1">2、正例 <a class="header-anchor" href="#_2、正例-1" aria-label="Permalink to &quot;2、正例&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">address</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">地址</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><h3 id="_3、理由-1" tabindex="-1">3、理由 <a class="header-anchor" href="#_3、理由-1" aria-label="Permalink to &quot;3、理由&quot;">​</a></h3><ol><li><code>varchar</code>变长字段按数据内容实际长度存储，存储空间小，可以节省存储空间；</li><li><code>char</code>按声明大小存储，不足补空格；</li><li>其次对于查询来说，在一个相对较小的字段内搜索，效率更高；</li></ol><h3 id="_4、技术延伸" tabindex="-1">4、技术延伸 <a class="header-anchor" href="#_4、技术延伸" aria-label="Permalink to &quot;4、技术延伸&quot;">​</a></h3><p>1、<code>char</code>的长度是固定的，而<code>varchar2</code>的长度是可以变化的。</p><p>比如，存储字符串<code>“101”</code>，对于<code>char(10)</code>，表示你存储的字符将占10个字节（包括7个空字符），在数据库中它是以空格占位的，而同样的<code>varchar2(10)</code>则只占用3个字节的长度，10只是最大值，当你存储的字符小于10时，按实际长度存储。</p><p>2、<code>char</code>的效率比<code>varchar2</code>的效率稍高。</p><p>3、何时用<code>char</code>，何时用<code>varchar2</code>?</p><p><code>char</code>和<code>varchar2</code>是一对矛盾的统一体，两者是互补的关系，<code>varchar2</code>比<code>char</code>节省空间，在效率上比<code>char</code>会稍微差一点，既想获取效率，就必须牺牲一点空间，这就是我们在数据库设计上常说的“以空间换效率”。</p><p><code>varchar2</code>虽然比<code>char</code>节省空间，但是假如一个<code>varchar2</code>列经常被修改，而且每次被修改的数据的长度不同，这会引起“行迁移”现象，而这造成多余的I/O，是数据库设计中要尽力避免的，这种情况下用<code>char</code>代替<code>varchar2</code>会更好一些。<code>char</code>中还会自动补齐空格，因为你<code>insert</code>到一个<code>char</code>字段自动补充了空格的,但是<code>select</code>后空格没有删除，因此<code>char</code>类型查询的时候一定要记得使用<code>trim</code>，这是写本文章的原因。</p><p>如果开发人员细化使用<code>rpad()</code>技巧将绑定变量转换为某种能与<code>char</code>字段相比较的类型（当然，与截断<code>trim</code>数据库列相比，填充绑定变量的做法更好一些，因为对列应用函数<code>trim</code>很容易导致无法使用该列上现有的索引），可能必须考虑到经过一段时间后列长度的变化。如果字段的大小有变化，应用就会受到影响，因为它必须修改字段宽度。</p><p>正是因为以上原因，定宽的存储空间可能导致表和相关索引比平常大出许多，还伴随着绑定变量问题，所以无论什么场合都要避免使用char类型。</p><h2 id="_28-清空表优先使用truncate" tabindex="-1">28. 清空表优先使用truncate <a class="header-anchor" href="#_28-清空表优先使用truncate" aria-label="Permalink to &quot;28. 清空表优先使用truncate&quot;">​</a></h2><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">truncate</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> user;</span></span></code></pre></div><p>1、<code>truncate table</code>在功能上与不带 <code>where</code>子句的 <code>delete</code>语句相同：二者均删除表中的全部行。但 <code>truncate table</code>比 <code>delete</code>速度快，且使用的系统和事务日志资源少。</p><p>2、<code>delete</code>语句每次删除一行，并在事务日志中为所删除的每行记录一项。<code>truncate table</code>通过释放存储表数据所用的数据页来删除数据，并且只在事务日志中记录页的释放。</p><p>3、<code>truncate table</code>删除表中的所有行，但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值，请改用 DELETE。如果要删除表定义及其数据，请使用 <code>drop table</code>语句。</p><p>4、对于由 <code>foreign key</code>约束引用的表，不能使用 <code>truncate table</code>，而应使用不带 <code>where</code>子句的 DELETE 语句。由于 <code>truncate table</code>不记录在日志中，所以它不能激活触发器。</p><p>5、<code>truncate table</code>不能用于参与了索引视图的表。</p><h2 id="_29-索引问题⭐" tabindex="-1">29. 索引问题⭐ <a class="header-anchor" href="#_29-索引问题⭐" aria-label="Permalink to &quot;29. 索引问题⭐&quot;">​</a></h2><h3 id="组合索引" tabindex="-1">组合索引 <a class="header-anchor" href="#组合索引" aria-label="Permalink to &quot;组合索引&quot;">​</a></h3><p>排序时应按照组合索引中各列的顺序进行排序，即使索引中只有一个列是要排序的，否则排序性能会比较差。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">index</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">IDX_USERNAME_TEL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> user(deptid,position,createtime);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> username,tel </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> deptid</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> position </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">java开发</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> deptid,position,createtime </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>实际上只是查询出符合 <code>deptid= 1 and position = &#39;java开发&#39;</code>条件的记录并按createtime降序排序，但写成order by createtime desc性能较差。</p><h3 id="复合索引最左特性" tabindex="-1">复合索引最左特性 <a class="header-anchor" href="#复合索引最左特性" aria-label="Permalink to &quot;复合索引最左特性&quot;">​</a></h3><p>1、创建复合索引</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">ALTER</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> employee </span><span style="color:#F78C6C;">ADD</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">INDEX</span><span style="color:#A6ACCD;"> idx_name_salary (</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,salary)</span></span></code></pre></div><p>2、满足复合索引的最左特性，哪怕只是部分，复合索引生效</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> employee </span><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NAME</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">哪吒编程</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p>3、没有出现左边的字段，则不满足最左特性，索引失效</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> employee </span><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> salary</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">5000</span></span></code></pre></div><p>4、复合索引全使用，按左侧顺序出现 name,salary，索引生效</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> employee </span><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NAME</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">哪吒编程</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AND</span><span style="color:#A6ACCD;"> salary</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">5000</span></span></code></pre></div><p>5、虽然违背了最左特性，但MySQL执行SQL时会进行优化，底层进行颠倒优化</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> employee </span><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> salary</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">5000</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AND</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NAME</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">哪吒编程</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p>6、理由</p><p>复合索引也称为联合索引，当我们创建一个联合索引的时候，如(k1,k2,k3)，相当于创建了（k1）、(k1,k2)和(k1,k2,k3)三个索引，这就是最左匹配原则。</p><p>联合索引不满足最左原则，索引一般会失效。</p><h1 id="mysql使用好习惯⭐" tabindex="-1">MySQL使用好习惯⭐ <a class="header-anchor" href="#mysql使用好习惯⭐" aria-label="Permalink to &quot;MySQL使用好习惯⭐&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzU1Nzg4NjgyMw==&amp;mid=2247487851&amp;idx=2&amp;sn=71f1903f135a58fd907a61440d93c213&amp;chksm=fc2fad63cb5824756800116db1b0f8cc84990482c0a38ddf062fa7d8a9656e4e8ddf5cc15817&amp;mpshare=1&amp;scene=23&amp;srcid=0510DW7fxxM6ivm9sya799xv&amp;sharer_sharetime=1652115030717&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">MySQL中，21个写SQL的好习惯！ (qq.com)</a></p><h2 id="sql性能优化" tabindex="-1">SQL性能优化 <a class="header-anchor" href="#sql性能优化" aria-label="Permalink to &quot;SQL性能优化&quot;">​</a></h2><h3 id="字段设置为not-null" tabindex="-1">字段设置为NOT NULL <a class="header-anchor" href="#字段设置为not-null" aria-label="Permalink to &quot;字段设置为NOT NULL&quot;">​</a></h3><p>在可能的情况下，尽量把字段设置为NOT NULL,这样在将来执行查询的时候，数据库不用去比较NULL值。</p><p>尽量把所有列定义为NOT NULL</p><ul><li><strong>「NOT NULL列更节省空间」</strong>，NULL列需要一个额外字节作为判断是否为 NULL 的标志位。</li><li><strong>「NULL列需要注意空指针问题」</strong>，NULL列在计算和比较的时候，需要注意空指针问题。</li></ul><blockquote><p>对于某些文本字段来说，例如“省份”或者“性别”，我们可以将他们定义为ENUM(枚举)类型。因为在MySQL中，ENUM类型被当做数值型数据来处理，而数值型数据被处理起来的速度要比文本类型要快得多。这样我们又可以提高数据库的性能。</p></blockquote><h3 id="字段为null问题" tabindex="-1">字段为NULL问题 <a class="header-anchor" href="#字段为null问题" aria-label="Permalink to &quot;字段为NULL问题&quot;">​</a></h3><p>列中使用<code>NULL</code>值容易引发不受控制的事情发生,有时候还会严重托慢系统的性能.</p><ul><li>对含有NULL值的列进行统计计算,eg. <code>count()</code>,<code>max()</code>,<code>min()</code>,结果并不符合我们的期望值.</li><li>干扰排序，分组,去重结果.</li><li>有的时候为了消除<code>NULL</code>带来的技术债务,我们需要在SQL中使用<code>IFNULL()</code>来确保结果可控,但是这使程序变得复杂.</li><li><code>NULL</code>值并是占用原有的字段空间存储,而是额外申请一个字节去标注,这个字段添加了<code>NULL</code>约束.(就像额外的标志位一样)</li></ul><blockquote><p>根据以上缺点,我们并不推荐在列中设置NULL作为列的默认值,你可以使用<code>NOT NULL</code>消除默认设置,使用<code>0</code>或者<code>&#39;&#39;</code>空字符串来代替<code>NULL</code>.</p></blockquote><h3 id="查看sql执行计划" tabindex="-1">查看SQL执行计划 <a class="header-anchor" href="#查看sql执行计划" aria-label="Permalink to &quot;查看SQL执行计划&quot;">​</a></h3><ol><li>写完SQL先explain查看执行计划（SQL性能优化）</li></ol><p>日常开发写SQL的时候，尽量养成这个好习惯呀：写完SQL后，用explain分析一下，尤其注意走不走索引。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userid,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">10086</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">or</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">18</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="优先使用索引列" tabindex="-1">优先使用索引列 <a class="header-anchor" href="#优先使用索引列" aria-label="Permalink to &quot;优先使用索引列&quot;">​</a></h3><p>2.写完SQL语句，检查where,order by,group by后面的列，<code>多表关联的列是否已加索引，优先考虑组合索引</code>。（SQL性能优化）</p><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">address</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">深圳</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> age;</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5/202205102154336.png" alt="image-20220510215443243" style="zoom:67%;"><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 添加索引</span></span>
<span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">index</span><span style="color:#A6ACCD;"> idx_address_age (</span><span style="color:#F78C6C;">address</span><span style="color:#A6ACCD;">,age)</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5/202205102155858.png" alt="image-20220510215501773" style="zoom:67%;"><h3 id="类型隐式转换" tabindex="-1">类型隐式转换 <a class="header-anchor" href="#类型隐式转换" aria-label="Permalink to &quot;类型隐式转换&quot;">​</a></h3><ol start="10"><li><code>where后面的字段，留意其数据类型的隐式转换</code>（SQL性能优化）</li></ol><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- userid 是varchar字符串类型</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> userid </span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">123</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「理由：」</strong></p><ul><li>因为不加单引号时，是字符串跟数字的比较，它们类型不匹配，MySQL会做隐式的类型转换，把它们转换为浮点数再做比较，最后导致索引失效</li></ul><h3 id="减少字段返回" tabindex="-1">减少字段返回 <a class="header-anchor" href="#减少字段返回" aria-label="Permalink to &quot;减少字段返回&quot;">​</a></h3><p>13.减少不必要的字段返回，如使用select &lt;具体字段&gt; 代替 select * （SQL性能优化）</p><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> employee;</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id，</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> employee;</span></span></code></pre></div><p>理由：</p><ul><li>节省资源、减少网络开销。</li><li>可能用到覆盖索引，减少回表，提高查询效率。</li></ul><h3 id="使用innodb存储引擎" tabindex="-1">使用Innodb存储引擎 <a class="header-anchor" href="#使用innodb存储引擎" aria-label="Permalink to &quot;使用Innodb存储引擎&quot;">​</a></h3><p>14.所有表必须使用Innodb存储引擎（SQL规范优雅）</p><p>Innodb <strong>「支持事务，支持行级锁，更好的恢复性」</strong>，高并发下性能更好，所以呢，没有特殊要求（即Innodb无法满足的功能如：列存储，存储空间数据等）的情况下，所有表必须使用Innodb存储引擎</p><h3 id="使用varchar代替-char" tabindex="-1">使用varchar代替 char <a class="header-anchor" href="#使用varchar代替-char" aria-label="Permalink to &quot;使用varchar代替 char&quot;">​</a></h3><ol start="16"><li>尽量使用varchar代替 char。（SQL性能优化）</li></ol><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">deptName</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">char</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">部门名称</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">deptName</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">部门名称</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p>理由：</p><ul><li>因为首先变长字段存储空间小，可以节省存储空间。</li></ul><h3 id="where后不跟函数计算" tabindex="-1">where后不跟函数计算 <a class="header-anchor" href="#where后不跟函数计算" aria-label="Permalink to &quot;where后不跟函数计算&quot;">​</a></h3><ol start="20"><li>WHERE从句中不对列进行函数转换和表达式计算</li></ol><p>假设loginTime加了索引</p><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId,loginTime </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> loginuser</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> Date_ADD(loginTime,Interval </span><span style="color:#F78C6C;">7</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">DAY</span><span style="color:#A6ACCD;">) </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain  </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> userId,loginTime </span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> loginuser </span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;">  loginTime </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> Date_ADD(</span><span style="color:#F78C6C;">NOW</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,INTERVAL </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">7</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">DAY</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p><strong>「理由：」</strong></p><ul><li>索引列上使用mysql的内置函数，索引失效</li></ul><h3 id="批量执行" tabindex="-1">批量执行 <a class="header-anchor" href="#批量执行" aria-label="Permalink to &quot;批量执行&quot;">​</a></h3><p>21.如果修改/更新数据过多，考虑批量进行。</p><p>反例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> account  </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100000</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>正例：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">for each(200次)</span></span>
<span class="line"><span style="color:#A6ACCD;">{</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> account  </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">500</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span></code></pre></div><p>理由：</p><ul><li>大批量操作会会造成主从延迟。</li><li>大批量操作会产生大事务，阻塞。</li><li>大批量操作，数据量过大，会把cpu打满。</li></ul><h2 id="sql规范优雅" tabindex="-1">SQL规范优雅 <a class="header-anchor" href="#sql规范优雅" aria-label="Permalink to &quot;SQL规范优雅&quot;">​</a></h2><h3 id="使用外键" tabindex="-1">使用外键 <a class="header-anchor" href="#使用外键" aria-label="Permalink to &quot;使用外键&quot;">​</a></h3><blockquote><p>锁定表的方法可以维护数据的完整性，但是他却不能保证数据的关联性。这个时候我们可以使用外键。例如：外键可以保证每一条销售记录都指向某一个存在的客户。</p></blockquote><p>在这里，外键可以把customerinfo表中的customerid映射到salesinfo表中customerid，任何一条没有办法合法customerid的记录都不会被跟新或插入到salesinfo中.</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">customerinfo</span><span style="color:#A6ACCD;">(customerid </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">primary key</span><span style="color:#A6ACCD;">) engine </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> innodb;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">salesinfo</span><span style="color:#A6ACCD;">( </span></span>
<span class="line"><span style="color:#A6ACCD;">    salesid </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">not null</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">    customerid  </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">not null</span><span style="color:#A6ACCD;">, </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">primary key</span><span style="color:#A6ACCD;">(customerid,salesid),</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">foreign key</span><span style="color:#A6ACCD;">(customerid) </span><span style="color:#C792EA;">references</span><span style="color:#A6ACCD;"> customerinfo(customerid) </span><span style="color:#C792EA;">on delete cascade</span></span>
<span class="line"><span style="color:#A6ACCD;">)engine </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> innodb;</span></span></code></pre></div><blockquote><p>注意例子中的参数“on delete cascade”.<code>该参数保证当customerinfo表中的一条客户记录也会被自动删除</code>。如果要在mysql中使用外键，一定要记住在创建表的时候将表的类型定义为事务安全表InnoDB类型。该类型不是mysql表的默认类型。定义的方法是在CREATE TABLE语句中加上engine=innoDB。</p></blockquote><h3 id="添加注释" tabindex="-1">添加注释 <a class="header-anchor" href="#添加注释" aria-label="Permalink to &quot;添加注释&quot;">​</a></h3><ol><li>设计表的时候，所有表和字段都添加相应的注释（SQL规范优雅）</li></ol><p>这个好习惯一定要养成啦，设计数据库表的时候，所有表和字段都添加相应的注释，后面更容易维护。</p><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">主键Id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户名</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">余额</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">创建时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">UPDATE</span><span style="color:#A6ACCD;"> CURRENT_TIMESTAMP COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">更新时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 ROW_FORMAT</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">REDUNDANT COMMENT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户表</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> ,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">UPDATE</span><span style="color:#A6ACCD;"> CURRENT_TIMESTAMP,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><ol start="2"><li>SQL书写格式，关键字大小保持一致，使用缩进。（SQL规范优雅）</li></ol><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> stu.name, </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(stu.score)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> Student stu</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> stu.classNo </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">1班</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#F78C6C;">GROUP BY</span><span style="color:#A6ACCD;"> stu.name</span></span></code></pre></div><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> stu.name, </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(stu.score) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> Student stu </span><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> stu.classNo </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">1班</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">group by</span><span style="color:#A6ACCD;"> stu.name.</span></span></code></pre></div><p>显然，统一关键字大小写一致，使用缩进对齐，会使你的SQL看起来更优雅~</p><ol start="3"><li>INSERT语句标明对应的字段名称（SQL规范优雅）</li></ol><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> Student </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">666</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">捡田螺的小男孩</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">100</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> Student(student_id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,score) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">666</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">捡田螺的小男孩</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">100</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><h3 id="表必备字段主键" tabindex="-1">表必备字段主键 <a class="header-anchor" href="#表必备字段主键" aria-label="Permalink to &quot;表必备字段主键&quot;">​</a></h3><p>设计数据库表的时候，加上三个字段：主键，create_time,update_time。（SQL规范优雅）</p><p><strong>「反例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户名</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">余额</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 ROW_FORMAT</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">REDUNDANT COMMENT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户表</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「正例：」</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">主键Id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户名</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">余额</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">创建时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">UPDATE</span><span style="color:#A6ACCD;"> CURRENT_TIMESTAMP COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">更新时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 ROW_FORMAT</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">REDUNDANT COMMENT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户表</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><strong>「理由：」</strong></p><ul><li>主键一般都要加上的，没有主键的表是没有灵魂的</li><li>创建时间和更新时间的话，还是建议加上吧，详细审计、跟踪记录，都是有用的。</li></ul><h3 id="字符集utf8mb4" tabindex="-1">字符集utf8mb4 <a class="header-anchor" href="#字符集utf8mb4" aria-label="Permalink to &quot;字符集utf8mb4&quot;">​</a></h3><p>数据库和表的字符集尽量统一使用UTF8（SQL规范优雅）</p><p>尽量统一使用UTF8编码</p><ul><li>可以避免乱码问题</li><li>可以避免，不同字符集比较转换，导致的索引失效问题</li></ul><p><strong>「如果需要存储表情，那么选择utf8mb4来进行存储，注意它与utf-8编码的区别。」</strong></p><h3 id="字段加注释" tabindex="-1">字段加注释 <a class="header-anchor" href="#字段加注释" aria-label="Permalink to &quot;字段加注释&quot;">​</a></h3><p>如果修改字段含义或对字段表示的状态追加时，需要及时更新字段注释。（SQL规范优雅）</p><p>这个点，是阿里开发手册中，Mysql的规约。你的字段，尤其是表示枚举状态时，如果含义被修改了，或者状态追加时，为了后面更好维护，需要即时更新字段的注释。</p><h3 id="索引名规范" tabindex="-1">索引名规范 <a class="header-anchor" href="#索引名规范" aria-label="Permalink to &quot;索引名规范&quot;">​</a></h3><p>索引命名要规范，<code>主键索引名为 pk_ 字段名；唯一索引名为 uk _字段名 ；普通索引名则为 idx _字段名</code>。说明：pk_即primary key；uk_即unique key；idx_即index 的简称。</p><h1 id="mysql防误操作" tabindex="-1">MySQL防误操作 <a class="header-anchor" href="#mysql防误操作" aria-label="Permalink to &quot;MySQL防误操作&quot;">​</a></h1><h2 id="_1-不要用聊天工具发sql语句" tabindex="-1">1.不要用聊天工具发sql语句 <a class="header-anchor" href="#_1-不要用聊天工具发sql语句" aria-label="Permalink to &quot;1.不要用聊天工具发sql语句&quot;">​</a></h2><p>通常开发人员写好sql语句之后，习惯通过聊天工具，比如：qq、钉钉、或者腾讯通等，发给<code>团队老大</code>或者<code>DBA</code>在线上环境执行。但由于有些聊天工具，对部分<code>特殊字符</code>会自动转义，而且有些消息由于<code>内容太长</code>，会被自动分成多条消息。</p><p>这样会导致团队老大或者DBA复制出来的sql不一定是正确的。</p><p>他们需要手动拼接成一条<code>完整的sql</code>，有时甚至需要把转义后的字符替换回以前的特殊字符，无形之中会浪费很多额外的时间。即使最终sql拼接好了，真正执行sql的人，心里一定很虚。</p><p>所以，强烈建议你把要在线上执行的sql语句用<code>邮件</code>发过去，可以避免使用聊天工具的一些弊端，减少一些误操作的机会。而且有个存档，方便今后有问题的时候回溯原因。很多聊天工具只保留最近<code>7天</code>的历史记录，邮件会保留更久一些。</p><p><strong>别用聊天工具发sql语句！</strong></p><p><strong>别用聊天工具发sql语句！</strong></p><p><strong>别用聊天工具发sql语句！</strong></p><p>重要的事情说三遍，它真的能减少一些误操作。</p><h2 id="_2-把sql语句压缩成一行" tabindex="-1">2.把SQL语句压缩成一行 <a class="header-anchor" href="#_2-把sql语句压缩成一行" aria-label="Permalink to &quot;2.把SQL语句压缩成一行&quot;">​</a></h2><p>有些时候，开发人员写的sql语句很长，使用了各种<code>join</code>和<code>union</code>，而且使用美化工具，将一条sql变成了多行。在复制sql的时候，自己都无法确定sql是否完整。（为了装逼，把自己也坑了，哈哈哈）</p><p>线上环境有时候需要通过命令行连接数据库，比如：mysql，你把sql语句复制过来后，在命令行界面执行，由于屏幕滚动太快，这时根本无法确定sql是否都执行成功。</p><p>针对这类问题，强烈建议把sql语句<code>压缩成一行</code>，去掉多余的<code>换行符</code>和<code>空格</code>，可以有效的减少一些误操作。</p><p><code>sql压缩工具</code>推荐使用：<a href="https://tool.lu/sql/" target="_blank" rel="noreferrer">https://tool.lu/sql/</a></p><h2 id="_3-操作数据之前先select一下" tabindex="-1">3.操作数据之前先select一下 <a class="header-anchor" href="#_3-操作数据之前先select一下" aria-label="Permalink to &quot;3.操作数据之前先select一下&quot;">​</a></h2><p>需要特别说明的是：本文的操作数据主要指<code>修改</code>和<code>删除</code>数据。</p><p>很多时候，由于我们人为失误，把where条件写错了。但没有怎么仔细检查，就把sql语句直接执行了。影响范围小还好，如果影响几万、几十万，甚至几百万行数据，我们可能要哭了。</p><p>针对这种情况，在操作数据之前，把sql先改成<code>select count(*)</code>语句，比如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>改成：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>查一下该sql执行后影响的记录行数，做到自己心中有数。也给自己一次测试sql是否正确，确认是否执行的机会。</p><h2 id="_4-操作数据sql加limit" tabindex="-1">4.操作数据SQL加limit <a class="header-anchor" href="#_4-操作数据sql加limit" aria-label="Permalink to &quot;4.操作数据SQL加limit&quot;">​</a></h2><p>即使通过上面的<code>select</code>语句确认了sql语句没有问题，执行后影响的记录行数是对的。</p><p>也建议你不要立刻执行，建议在正在执行的时候，加上<code>limit</code> + <code>select出的记录行数</code>。例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1000</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>假设有一次性更新的数据太多，所有相关记录行都会被锁住，造成长时间的锁等待，而造成用户请求超时。</p><p>此外，加<code>limit</code>可以避免一次性操作太多数据，对服务器的cpu造成影响。</p><p>还有一个最重要的原因：加<code>limit</code>后，操作数据的影响范围是完全可控的。</p><p>删改加limit</p><p><code>操作delete或者update语句，加个limit(SQL后悔药）</code></p><p>在执行删除或者更新语句，尽量加上limit，以下面的这条 SQL 为例吧：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">delete</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> euser </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> age </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">30</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">200</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>因为加了limit 主要有这些好处：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.5/202205102146361.png" alt="image-20220510214613262" style="zoom:80%;"><ul><li><strong>「降低写错SQL的代价」</strong>, 你在命令行执行这个SQL的时候，如果不加limit，执行的时候一个**「不小心手抖」<strong>，可能数据全删掉了，如果</strong>「删错」**了呢？加了limit 200，就不一样了。删错也只是丢失200条数据，可以通过binlog日志快速恢复的。</li><li><strong>「SQL效率很可能更高」</strong>，你在SQL行中，加了limit 1，如果第一条就命中目标return， 没有limit的话，还会继续执行扫描表。</li><li><strong>「避免了长事务」</strong>，delete执行时,如果age加了索引，MySQL会将所有相关的行加写锁和间隙锁，所有执行相关行会被锁住，如果删除数量大，会直接影响相关业务无法使用。</li><li><strong>「数据量大的话，容易把CPU打满」</strong> ,如果你删除数据量很大时，不加 limit限制一下记录数，容易把cpu打满，导致越删越慢的。</li></ul><h2 id="_5-update时更新修改人和修改时间" tabindex="-1">5.update时更新修改人和修改时间 <a class="header-anchor" href="#_5-update时更新修改人和修改时间" aria-label="Permalink to &quot;5.update时更新修改人和修改时间&quot;">​</a></h2><p>很多人写<code>update</code>语句时，如果要修改状态，就只更新状态，不管其他的字段。比如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>这条sql会把<code>status</code>等于0的数据，全部更新成1。</p><p>后来发现业务逻辑有问题，不应该这么更新，需要把<code>status</code>状态回滚。</p><p>这时你可能会很自然想到这条sql：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>但仔细想想又有些不对。</p><p>这样不是会把有部分以前<code>status</code>就是1的数据更新成0？</p><p>这回真的要哭了，呜呜呜。</p><p>这时，送你一个好习惯：在更新数据的时候，同时更新<code>修改人</code>和<code>修改时间</code>字段。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;"># 这样在恢复数据时就能通过修改人和修改时间字段过滤数据了</span></span>
<span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,edit_date</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,edit_user</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">admin</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>后面需要用到的<code>修改时间</code>通过这条sql语句可以轻松找到：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> edit_user ,edit_date </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> edit_date </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">50</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><blockquote><p>当然，如果是高并发系统不建议这种批量更新方式，可能会锁表一定时间，造成请求超时。</p></blockquote><p>有些同学可能会问：为什么要同时更新修改人，只更新修改时间不行吗？</p><p>主要有如下的原因：</p><ol><li>为了标识非正常用户操作，方便后面统计和定位问题。</li><li>有些情况下，在执行sql语句的过程中，正常用户产生数据的修改时间跟你的可能一模一样，导致回滚时数据查多了。</li></ol><h2 id="_6-多用逻辑删除-少用物理删除" tabindex="-1">6.多用逻辑删除，少用物理删除 <a class="header-anchor" href="#_6-多用逻辑删除-少用物理删除" aria-label="Permalink to &quot;6.多用逻辑删除，少用物理删除&quot;">​</a></h2><p>在业务开发中，删除数据是必不可少的一种业务场景。</p><p>有些人开发人员习惯将表设计成<code>物理删除</code>，根据主键只用一条<code>delete</code>语句就能轻松搞定。</p><p>他们给出的理由是：<code>节省数据库的存储空间</code>。</p><p>想法是好的，但是现实很残酷。</p><p>如果有条极重要的数据删错了，想恢复怎么办？</p><p>此时只剩八个字：没有数据，恢复不了。（PS：或许通过binlog二进制文件可以恢复）</p><p>如果之前设计表的时候用的<code>逻辑删除</code>，上面的问题就变得好办了。删除数据时，只需<code>update</code>删除状态即可，例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> del_status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,edit_date</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,edit_user</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">admin</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>假如出现异常，要恢复数据，把该id的删除状态还原即可，例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> del_status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">,edit_date</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,edit_user</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">admin</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h2 id="_7-操作数据之前先做备份" tabindex="-1">7.操作数据之前先做备份 <a class="header-anchor" href="#_7-操作数据之前先做备份" aria-label="Permalink to &quot;7.操作数据之前先做备份&quot;">​</a></h2><p>如果只是修改了少量的数据，或者只执行了一两条sql语句，通过上面的<code>修改人</code>和<code>修改时间</code>字段，在需要回滚时，能快速的定位到正确的数据。</p><p>但是如果修改的记录行数很多，并且执行了多条sql，产生了很多<code>修改时间</code>。这时，你可能就要犯难了，没法一次性找出哪些数据需要回滚。</p><p>为了解决这类问题，可以将表做备份。</p><p>可以使用如下sql备份：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">order_bak_2021031721</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> order_bak_2021031721 </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>先创建一张一模一样的表，然后把数据复制到新表中。</p><p>也可以简化成一条sql：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">order_bak_2021031722</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 创建表的同时复制数据到新表中。</span></span></code></pre></div><blockquote><p>此外，建议在表名中加上<code>bak</code>和<code>时间</code>，一方面是为了通过表名快速识别出哪些表是备份表，另一方面是为了备份多次时好做区分。因为有时需要执行多次sql才能把数据修复好，这种情况建议把表备份多次，如果出现异常，把数据回滚到最近的一次备份，可以节省很多重复操作的时间。</p></blockquote><p>恢复数据时，把sql语句改成<code>select</code>语句，先在备份库找出相关数据，每条数据对应一条<code>update</code>语句，还原到老表中。</p><h2 id="_8-中间结果写入临时表" tabindex="-1">8.中间结果写入临时表 <a class="header-anchor" href="#_8-中间结果写入临时表" aria-label="Permalink to &quot;8.中间结果写入临时表&quot;">​</a></h2><p>有时候，我们要先用一条sql查询出要更新的记录的id，然后通过这些id更新数据。</p><p>批量更新之后，发现不对，要回滚数据。但由于有些数据已更新，此时使用相同的sql相同的条件，却查不出上次相同的id了。</p><p>这时，我们开始慌了。</p><p>针对这种情况，我们可以先将第一次查询的id存入一张<code>临时表</code>，然后通过<code>临时表</code>中的id作为查询条件更新数据。</p><p>如果要恢复数据，只用通过<code>临时</code>表中的id作为查询条件更新数据即可。</p><p>修改完，3天之后，如果没有出现问题，就可以把<code>临时表</code>删掉了。</p><h2 id="_9-表名前面一定要带库名" tabindex="-1">9.表名前面一定要带库名 <a class="header-anchor" href="#_9-表名前面一定要带库名" aria-label="Permalink to &quot;9.表名前面一定要带库名&quot;">​</a></h2><p>我们在写sql时为了方便，习惯性不带数据库名称。比如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,edit_date</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,edit_user</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">admin</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> 假如有多个数据库中有相同的表order，表结构一模一样，只是数据不一样。</span></span></code></pre></div><p>由于执行sql语句的人一个小失误，进错数据库了。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">use</span><span style="color:#A6ACCD;"> trade1;</span></span></code></pre></div><p>然后执行了这条sql语句，结果悲剧了。</p><p>有个非常有效的预防这类问题的方法是加<code>数据库名</code>：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">trade2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,edit_date</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;">,edit_user</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">admin</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">-- 这样即使执行sql语句前进错数据库了，也没什么影响。</span></span></code></pre></div><h2 id="_10-字段增删改的限制" tabindex="-1">10.字段增删改的限制 <a class="header-anchor" href="#_10-字段增删改的限制" aria-label="Permalink to &quot;10.字段增删改的限制&quot;">​</a></h2><p>很多时候，我们少不了对表字段的操作，比如：新加、修改、删除字段，但每种情况都不一样。</p><h3 id="新加的字段一定要允许为空" tabindex="-1">新加的字段一定要允许为空 <a class="header-anchor" href="#新加的字段一定要允许为空" aria-label="Permalink to &quot;新加的字段一定要允许为空&quot;">​</a></h3><p>新加的字段一定要允许为空。为什么要这样设计呢？</p><p>正常情况下，如果程序新加了字段，一般是先在数据库中加字段，然后再发程序的最新代码。</p><p>为什么是这种顺序？</p><p>因为如果先发程序，然后在数据库中加字段。在该程序刚部署成功，但数据库新字段还没来得及加的这段时间内，最新程序中，所有使用了新加字段的增删改查sql都会报字段不存在的异常。</p><p>好了，就按先在数据库中加字段，再发程序的顺序。</p><p>如果数据库中新加的字段非空，最新的程序还没发，线上跑的还是老代码，这时如果有<code>insert</code>操作，就会报字段不能为空的异常。因为新加的非空字段，老代码是没法赋值的。</p><p>所以说新加的字段一定要允许为空。</p><p>除此之外，这种设计更多的考虑是为了程序发布失败时的回滚操作。如果新加的字段允许为空，则可以不用回滚数据库，只需回滚代码即可，是不是很方便？</p><h3 id="不允许删除字段" tabindex="-1">不允许删除字段 <a class="header-anchor" href="#不允许删除字段" aria-label="Permalink to &quot;不允许删除字段&quot;">​</a></h3><p>删除字段是不允许的，特别是必填字段一定不能删除。</p><p>为什么这么说？</p><p>假设开发人员已经把程序改成不使用删除字段了，接下来如何部署呢？</p><ol><li>如果先把程序部署好了，还没来得及删除数据库相关表字段。当有<code>insert</code>请求时，由于数据库中该字段是必填的，会报必填字段不能为空的异常。</li><li>如果先把数据库中相关表字段删了，程序还没来得及发。这时所有涉及该删除字段的<code>增删改查</code>，都会报字段不存在的异常。</li></ol><p>所以，线上环境必填字段一定不能删除的。</p><h3 id="根据实际情况修改字段" tabindex="-1">根据实际情况修改字段 <a class="header-anchor" href="#根据实际情况修改字段" aria-label="Permalink to &quot;根据实际情况修改字段&quot;">​</a></h3><p>修改字段要分为这三种情况：</p><h4 id="_1-修改字段名称" tabindex="-1"><strong>1.修改字段名称</strong> <a class="header-anchor" href="#_1-修改字段名称" aria-label="Permalink to &quot;**1.修改字段名称**&quot;">​</a></h4><p>修改字段名称也不允许，跟删除必填字段的问题差不多。</p><ol><li>如果把程序部署好了，还没来得及修改数据库中表字段名称。这时所有涉及该字段的<code>增删改查</code>，都会报字段不存在的异常。</li><li>如果先把数据库中字段名称改了，程序还没来得及发。这时所有涉及该字段的<code>增删改查</code>，同样也会报字段不存在的异常。</li></ol><p>所以，线上环境字段名称一定不要修改。</p><h4 id="_2-修改字段类型" tabindex="-1"><strong>2.修改字段类型</strong> <a class="header-anchor" href="#_2-修改字段类型" aria-label="Permalink to &quot;**2.修改字段类型**&quot;">​</a></h4><p>修改字段类型时一定要兼容之前的数据。例如：</p><ol><li>tinyint改成int可以，但int改成tinyint要仔细衡量一下。</li><li>varchar改成text可以，但text改成varchar要仔细衡量一下。</li></ol><h4 id="_3-修改字段长度" tabindex="-1"><strong>3.修改字段长度</strong> <a class="header-anchor" href="#_3-修改字段长度" aria-label="Permalink to &quot;**3.修改字段长度**&quot;">​</a></h4><p>字段长度建议改大，通常情况下，不建议改小。如果一定要改小，要先确认该字段可能会出现的最大长度，避免<code>insert</code>操作时出现字段太长的异常。</p><p>此外，建议改大也需要设置一个合理的长度，避免数据库资源浪费。</p><h1 id="mysql配置优化⭐" tabindex="-1">MySQL配置优化⭐ <a class="header-anchor" href="#mysql配置优化⭐" aria-label="Permalink to &quot;MySQL配置优化⭐&quot;">​</a></h1><p>既然谈到优化，一定想到要从多个维度进行优化。</p><p>这里的优化维度有四个：<strong>SQL语句及索引</strong>、<strong>表结构设计</strong>、<strong>系统配置</strong>、<strong>硬件配置</strong></p><p>其中SQL语句相关的优化手段是最为重要的。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img/image-20220314174530735.png" alt="image-20220314174530735" style="zoom:67%;"><h2 id="_1-连接配置优化" tabindex="-1">1. 连接配置优化 <a class="header-anchor" href="#_1-连接配置优化" aria-label="Permalink to &quot;1. 连接配置优化&quot;">​</a></h2><p>处理连接是MySQL客户端和MySQL服务端亲热的第一步，第一步都迈不好，也就别谈后来的故事了。</p><p>正常情况下，客户端与server层如果只有<strong>一条</strong>连接，那么在执行sql查询之后，只能阻塞等待结果返回，如果有大量查询同时并发请求，那么<strong>后面的请求都需要等待前面的请求执行完成</strong>后，才能开始执行。</p><p>如果我们能<strong>多建几条连接</strong>，那么请求就可以并发执行，后面的连接就不用等那么久了。</p><p>既然连接是双方的事情，我们自然从服务端和客户端两个方面来进行优化喽。</p><h3 id="_1-服务端配置" tabindex="-1">1. 服务端配置 <a class="header-anchor" href="#_1-服务端配置" aria-label="Permalink to &quot;1. 服务端配置&quot;">​</a></h3><p>服务端需要做的就是尽可能地多接受客户端的连接，或许你遇到过<code>error 1040: Too many connections</code>的错误？就是服务端的胸怀不够宽广导致的，格局太小！</p><p>我们可以从两个方面解决连接数不够的问题：</p><ol><li><code>增加可用连接数</code>，修改环境变量<code>max_connections</code>，默认情况下服务端的最大连接数为<code>151</code>个</li></ol><div class="language-mysql"><button title="Copy Code" class="copy"></button><span class="lang">mysql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">mysql&gt; show variables like &#39;max_connections&#39;;</span></span>
<span class="line"><span style="color:#A6ACCD;">+-----------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| Variable_name   | Value |</span></span>
<span class="line"><span style="color:#A6ACCD;">+-----------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| max_connections | 151   |</span></span>
<span class="line"><span style="color:#A6ACCD;">+-----------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">1 row in set (0.01 sec)</span></span></code></pre></div><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show </span><span style="color:#F78C6C;">global</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">max_used_connections</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206221522452.png" alt="image-20220622152230346" style="zoom:67%;"><p><code>可以看到服务器响应的最大连接数为1，远远低于MySQL 服务器允许的最大连接数值</code></p><p>直接在my.ini中新增即可</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> 设置用户连接数</span></span>
<span class="line"><span style="color:#A6ACCD;">max_connections</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">500</span></span></code></pre></div><blockquote><p>对于 MySOL 服务器最大连接数值的设置范围比较理想的是:服务器响应的最大连接数值占服务器上限连接数值的比例值在10%以上，如果在10%以下，说明MySQL 服务器最大连接上限值设置过高 Max used connections / max connections * 100%= 1/151 *100%~ 0.6% 可以看到占比远低于10%。</p></blockquote><blockquote><p>注意:这是本地测试服务器，结果值没有太大的实际意义，将来可以根据实际情况设置连接数的上限值。</p></blockquote><p>及时释放不活动的连接，系统默认的客户端超时时间是28800秒（8小时），我们可以把这个值调小一点</p><div class="language-mysql"><button title="Copy Code" class="copy"></button><span class="lang">mysql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">mysql&gt; show variables like &#39;wait_timeout&#39;;</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| Variable_name | Value |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| wait_timeout  | 28800 |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+-------+</span></span>
<span class="line"><span style="color:#A6ACCD;">1 row in set (0.01 sec)</span></span></code></pre></div><blockquote><p>MySQL有非常多的配置参数，并且大部分参数都提供了默认值，默认值是MySQL作者经过精心设计的，完全可以满足大部分情况的需求，不建议在不清楚参数含义的情况下贸然修改。</p></blockquote><h3 id="_2-客户端优化" tabindex="-1">2. 客户端优化 <a class="header-anchor" href="#_2-客户端优化" aria-label="Permalink to &quot;2. 客户端优化&quot;">​</a></h3><p>客户端能做的就是尽量减少和服务端建立连接的次数，已经建立的连接能凑合用就凑合用，别每次执行个SQL语句都创建个新连接，服务端和客户端的资源都吃不消啊。</p><p>解决的方案就是使用<strong>连接池</strong>来复用连接。</p><p>常见的数据库连接池有<code>DBCP</code>、<code>C3P0</code>、阿里的<code>Druid</code>、<code>Hikari</code>，前两者用得很少了，后两者目前如日中天。</p><p>但是需要注意的是连接池并不是越大越好，比如<code>Druid</code>的默认最大连接池大小是8，<code>Hikari</code>默认最大连接池大小是10，盲目地加大连接池的大小，系统执行效率反而有可能降低。为什么？</p><p>对于每一个连接，服务端会创建一个单独的线程去处理，连接数越多，服务端创建的线程自然也就越多。而线程数超过CPU个数的情况下，CPU势必要通过分配时间片的方式进行线程的上下文切换，频繁的上下文切换会造成很大的性能开销。</p><p><a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fbrettwooldridge%2FHikariCP%2Fwiki%2FAbout-Pool-Sizing" target="_blank" rel="noreferrer">Hikari官方</a>给出了一个<code>PostgreSQL</code>数据库连接池大小的建议值公式，<code>CPU核心数*2+1</code>。假设服务器的CPU核心数是4，把连接池设置成9就可以了。这种公式在一定程度上对其他数据库也是适用的，大家面试的时候可以吹一吹。</p><h3 id="_3-设置超时参数" tabindex="-1">3. 设置超时参数 <a class="header-anchor" href="#_3-设置超时参数" aria-label="Permalink to &quot;3. 设置超时参数&quot;">​</a></h3><p>有时候，由于业务的复杂性，在JVM中拼装一些数据，会造成资源的极大浪费。举个例子，从MySQL中查询出一个List，然后在代码里循环查询数据库，进行一些字段的填充。</p><p>这种数据组装方式，除了执行效率的问题，往往会有更多的内存占用，对整个JVM计算节点造成了比较大的压力，有时候甚至造成内存溢出。于是，一些比较<strong>牛X</strong>的开发人员，使用非常复杂的SQL，来把这些耗时的操作，转嫁给数据库。</p><p>可怜的数据库，成了最后一道屏障。谁让数据库的配置普遍都比较高呢？活该。</p><p>但是可惜的是，数据库完成这些动作，同样要经历耗时的操作。Java线程等的不耐烦了，就会对用户直接返回超时，懵逼的用户会在这种情况下，再次发起重试。</p><p>要知道，Java端超时，并不代表发起的请求就结束运行了，这在一些高并发的场景中，可怜的数据库会空跑一些耗时的慢查询，计算着一些无人能知的数据。</p><p>对于mysql数据库，有两个可用的参数：</p><p><strong>connectTimeout</strong></p><p>默认值：0，单位：毫秒 配置连接超时时间，通过 Socket 对象的 connect(SocketAddress endpoint, int timeout) 方法来配置</p><p>当设置 connectTimeout=1时，在建立数据库连接时即发生了错误。<code>该值在建立数据库连接时有效</code>。</p><p><strong>socketTimeout</strong></p><p>默认值：0，单位：ms 配置socket的超时时间，通过 Socket 对象的 setSoTimeout(int timeout) 方法来配置</p><p>构造一个慢查，并设置socketTimeout小于慢查的时间，如：socketTimeout=1000&amp;connectTimeout=1000，慢查执行时间100S。</p><p>示例：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">jdbc</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;">mysql:</span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">xxx</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">xx</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">xxx</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">xxx:</span><span style="color:#F78C6C;">3306</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">database</span><span style="color:#89DDFF;">?</span><span style="color:#A6ACCD;">connectTimeout</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">60000</span><span style="color:#89DDFF;">&amp;</span><span style="color:#A6ACCD;">socketTimeout</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">60000</span></span></code></pre></div><h2 id="_2-保证从内存读取" tabindex="-1">2. 保证从内存读取 <a class="header-anchor" href="#_2-保证从内存读取" aria-label="Permalink to &quot;2. 保证从内存读取&quot;">​</a></h2><p>MySQL 会在内存中保存一定的数据，通过 <strong>LRU（最近最少使用）算法</strong>将不常访问的数据保存在硬盘文件中。尽可能的扩大内存中的数据量，将数据保存在内存中，从内存中读取数据，可以提升 MySQL 性能。</p><p>LRU 算法针对的是 MySQL 内存中的结构，这里有个区域叫 <strong>Buffer Pool（缓冲池）</strong> 作为数据读写的缓冲区域。把这个区域进行相应的扩大即可提升性能，当然这个参数要针对服务器硬件的实际情况进行调整。</p><p>我们在前面的数据库查询流程里，提到了进了innodb之后，会有一层内存buffer pool，用于将磁盘数据页加载到内存页中，只要查询到buffer pool里有，就可以直接返回，否则就要走磁盘IO，那就慢了。</p><blockquote><p>也就是说，如果我的buffer pool 越大，那我们能放的数据页就越多，相应的，sql查询时就更可能命中buffer pool，那查询速度自然就更快了。</p></blockquote><h3 id="配置buffer-pool大小" tabindex="-1">配置buffer_pool大小 <a class="header-anchor" href="#配置buffer-pool大小" aria-label="Permalink to &quot;配置buffer_pool大小&quot;">​</a></h3><p>通过以下命令可以查看相应的BufferPool的相关参数：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show </span><span style="color:#F78C6C;">global</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">innodb_buffer_pool_pages_%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><img src="https://mmbiz.qpic.cn/mmbiz_png/mngWTkJEOYKht9kicH7icGHOvAiav2hRhq8UZdzTzGibwsn2C6GpunpkEQ3NS4icv6OhN1SdkURo6c0uySNibwInvP5g/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片" style="zoom:67%;"><p>输入以下命令可以查看 BufferPool 的大小：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">%innodb_buffer_pool_size%</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>在这里我们可以修改这个参数的值，如果该服务器是 MySQL 专用的服务器，我们可以 <strong>修改为总内存的 60%~80%</strong> ，当然不能影响系统程序的运行。</p><p>这个参数是只读的，可以在 MySQL 的配置文件（my.cnf 或 my.ini）中进行修改。Linux 的配置文件为 <strong>my.cnf</strong>。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;"># 修改缓冲池大小为750M</span></span>
<span class="line"><span style="color:#A6ACCD;">innodb_buffer_pool_size </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> 750M</span></span></code></pre></div><h3 id="判断buffer-pool是否太小" tabindex="-1">判断buffer_pool是否太小 <a class="header-anchor" href="#判断buffer-pool是否太小" aria-label="Permalink to &quot;判断buffer_pool是否太小&quot;">​</a></h3><h3 id="怎么知道buffer-pool是不是太小了" tabindex="-1">怎么知道buffer pool是不是太小了？ <a class="header-anchor" href="#怎么知道buffer-pool是不是太小了" aria-label="Permalink to &quot;怎么知道buffer pool是不是太小了？&quot;">​</a></h3><p>这个我们可以看<strong>buffer pool的缓存命中率</strong>。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show </span><span style="color:#F78C6C;">status</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Innodb_buffer_pool_%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.11/202206071452329.png" alt="image-20220607145201244" style="zoom:80%;"><p>通过 <code>show status like &#39;Innodb_buffer_pool_%&#39;;</code>可以看到跟buffer pool有关的一些信息。</p><p><code>Innodb_buffer_pool_read_requests</code>表示读请求的次数。</p><p><code>Innodb_buffer_pool_reads</code> 表示从物理磁盘中读取数据的请求次数。</p><p>所以buffer pool的命中率就可以这样得到：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">buffer</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">pool</span><span style="color:#A6ACCD;"> 命中率 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">-</span><span style="color:#A6ACCD;"> (Innodb_buffer_pool_reads</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">Innodb_buffer_pool_read_requests) </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">%</span></span></code></pre></div><p>比如我上面截图里的就是，1 - (437/3048) = 86.98%。</p><p>一般情况下<strong>buffer pool命中率</strong>都在<code>99%</code>以上，如果低于这个值，才需要考虑加大innodb buffer pool的大小。</p><p>当然，还可以把这个命中率做到<strong>监控</strong>里，这样半夜sql变慢了，早上上班还能定位到原因，就很舒服。</p><h2 id="_3-降低磁盘的写入次数" tabindex="-1">3. 降低磁盘的写入次数 <a class="header-anchor" href="#_3-降低磁盘的写入次数" aria-label="Permalink to &quot;3. 降低磁盘的写入次数&quot;">​</a></h2><p>增大redo log，减少落盘次数：</p><p>redo log是重做日志，用于保证数据的一致，减少落盘相当于减少了系统 IO 操作。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">innodb_log_file_size 设置为 </span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">.</span><span style="color:#F78C6C;">25</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> innodb_buffer_pool_size</span></span></code></pre></div><p>通用查询日志、慢查询日志可以不开 ，binlog 可开启</p><p>通用查询和慢查询日志也是要落盘的，可以根据实际情况开启，如果不需要使用的话就可以关掉。binlog 用于恢复和主从复制，这个可以开启。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;"># 慢查询日志</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">slow_query_log%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"># 通用查询日志</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%general%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"># 错误日志</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%log_error%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"># 二进制日志</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%binlog%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>innodb_flush_log_at_trx_commit 设置为 0 或 2，默认是1</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%innodb_flush_log_at_trx_commit%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>对于不需要强一致性的业务，可以设置为 0 或 2。</p><ul><li>0：每隔 1 秒写日志文件和刷盘操作（写日志文件 LogBuffer --&gt; OS cache，刷盘 OS cache --&gt; 磁盘文件），最多丢失 1 秒数据</li><li>1：事务提交，立刻写日志文件和刷盘，数据不丢失，但是会频繁 IO 操作</li><li>2：事务提交，立刻写日志文件，每隔 1 秒钟进行刷盘操作</li></ul><h2 id="其余参数调优" tabindex="-1">其余参数调优 <a class="header-anchor" href="#其余参数调优" aria-label="Permalink to &quot;其余参数调优&quot;">​</a></h2><h3 id="back-log" tabindex="-1">back_log <a class="header-anchor" href="#back-log" aria-label="Permalink to &quot;back_log&quot;">​</a></h3><p>back_log值可以指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说，如果MySQL的连接数据达到max_connections时，新来的请求将会被存在堆栈中，以等待某一连接释放资源，该堆栈的数量即back_log，如果等待连接的数量超过back_log，将不被授予连接资源。<strong>可以从默认的50升至500</strong>。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 默认查找是80</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%back_log%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="wait-timeout" tabindex="-1">wait_timeout <a class="header-anchor" href="#wait-timeout" aria-label="Permalink to &quot;wait_timeout&quot;">​</a></h3><p>数据库连接闲置时间，闲置连接会占用内存资源。可以从默认的8小时减到半小时。</p><p>查询显示28800秒，进行转换就是8小时</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%wait_timeout%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="max-user-connection" tabindex="-1">max_user_connection <a class="header-anchor" href="#max-user-connection" aria-label="Permalink to &quot;max_user_connection&quot;">​</a></h3><p>最大连接数，默认为0无上限，最好设一个合理上限。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%max_user_connection%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="thread-concurrency" tabindex="-1">thread_concurrency <a class="header-anchor" href="#thread-concurrency" aria-label="Permalink to &quot;thread_concurrency&quot;">​</a></h3><p>并发线程数，设为CPU核数的两倍。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 默认是33</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%thread_concurrency%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="skip-name-resolve" tabindex="-1">skip_name_resolve <a class="header-anchor" href="#skip-name-resolve" aria-label="Permalink to &quot;skip_name_resolve&quot;">​</a></h3><p>禁止对外部连接进行DNS解析，消除DNS解析时间，但需要所有远程主机用IP访问。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 默认OFF</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%skip_name_resolve%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="key-buffer-size" tabindex="-1">key_buffer_size <a class="header-anchor" href="#key-buffer-size" aria-label="Permalink to &quot;key_buffer_size&quot;">​</a></h3><p>索引块的缓存大小，增加会提升索引处理速度，对MyISAM表性能影响最大。对于内存4G左右，可设为256M或384M，通过查询show status like &#39;key_read%&#39;，保证key_reads / key_read_requests在0.1%以下最好。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 8388608</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%key_buffer_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="innodb-buffer-pool-size" tabindex="-1">innodb_buffer_pool_size <a class="header-anchor" href="#innodb-buffer-pool-size" aria-label="Permalink to &quot;innodb_buffer_pool_size&quot;">​</a></h3><p>缓存数据块和索引块，对InnoDB表性能影响最大。保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 8388608</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%innodb_buffer_pool_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="innodb-additional-mem-pool-size" tabindex="-1">innodb_additional_mem_pool_size <a class="header-anchor" href="#innodb-additional-mem-pool-size" aria-label="Permalink to &quot;innodb_additional_mem_pool_size&quot;">​</a></h3><blockquote><p>InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小，当数据库对象非常多的时候，适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率，当过小的时候，MySQL会记录Warning信息到数据库的错误日志中，这时就需要该调整这个参数大小。</p></blockquote><h3 id="innodb-log-buffer-size" tabindex="-1">innodb_log_buffer_size <a class="header-anchor" href="#innodb-log-buffer-size" aria-label="Permalink to &quot;innodb_log_buffer_size&quot;">​</a></h3><p>InnoDB存储引擎的事务日志所使用的缓冲区，一般来说不建议超过32MB。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 1048576</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%innodb_log_buffer_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="query-cache-size" tabindex="-1">query_cache_size <a class="header-anchor" href="#query-cache-size" aria-label="Permalink to &quot;query_cache_size&quot;">​</a></h3><blockquote><p>缓存MySQL中的ResultSet，也就是一条SQL语句执行的结果集，所以仅仅只能针对select语句。当某个表的数据有任何变化，都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以，当我们数据变化非常频繁的情况下，使用Query Cache可能得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整，一般不建议太大，256MB可能已经差不多了，大型的配置型静态数据可适当调大。可以通过命令show status like &#39;Qcache_%&#39;查看目前系统Query catch使用大小。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 1048576</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%query_cache_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="read-buffer-size" tabindex="-1">read_buffer_size <a class="header-anchor" href="#read-buffer-size" aria-label="Permalink to &quot;read_buffer_size&quot;">​</a></h3><blockquote><p>MySQL读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区，MySQL会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁，可以通过增加该变量值以及内存缓冲区大小来提高其性能。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 8192</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%read_buffer_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="sort-buffer-size" tabindex="-1">sort_buffer_size <a class="header-anchor" href="#sort-buffer-size" aria-label="Permalink to &quot;sort_buffer_size&quot;">​</a></h3><blockquote><p>MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度，首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能，可以尝试增加sort_buffer_size变量的大小。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 262144</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%sort_buffer_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="read-rnd-buffer-size" tabindex="-1">read_rnd_buffer_size <a class="header-anchor" href="#read-rnd-buffer-size" aria-label="Permalink to &quot;read_rnd_buffer_size&quot;">​</a></h3><blockquote><p>MySQL的随机读缓冲区大小。当按任意顺序读取行时(例如按照排序顺序)，将分配一个随机读缓存区。进行排序查询时，MySQL会首先扫描一遍该缓冲，以避免磁盘搜索，提高查询速度，如果需要排序大量数据，可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间，所以应尽量适当设置该值，以避免内存开销过大。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 默认1</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%read_rnd_buffer_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="record-buffer" tabindex="-1">record_buffer <a class="header-anchor" href="#record-buffer" aria-label="Permalink to &quot;record_buffer&quot;">​</a></h3><p>每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描，可能想要增加该值。</p><h3 id="thread-cache-size" tabindex="-1">thread_cache_size <a class="header-anchor" href="#thread-cache-size" aria-label="Permalink to &quot;thread_cache_size&quot;">​</a></h3><blockquote><p>保存当前没有与连接关联但是准备为后面新的连接服务的线程，可以快速响应连接的线程请求而无需创建新的。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#676E95;font-style:italic;">-- 默认是10</span></span>
<span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%thread_cache_size%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h1 id="mysql表结构优化⭐" tabindex="-1">MySQL表结构优化⭐ <a class="header-anchor" href="#mysql表结构优化⭐" aria-label="Permalink to &quot;MySQL表结构优化⭐&quot;">​</a></h1><h2 id="表设计优化" tabindex="-1">表设计优化 <a class="header-anchor" href="#表设计优化" aria-label="Permalink to &quot;表设计优化&quot;">​</a></h2><h3 id="尽量避免使用null" tabindex="-1">尽量避免使用NULL <a class="header-anchor" href="#尽量避免使用null" aria-label="Permalink to &quot;尽量避免使用NULL&quot;">​</a></h3><p>NULL在MySQL中不好处理，存储需要额外空间，运算也需要特殊的运算符，含有NULL的列很难进行查询优化</p><p>应当指定列为not null，用0、空串或其他特殊的值代替空值，比如定义为int not null default 0</p><p>非空字段尽量设置成<code>NOT NULL</code>，并提供默认值，或者使用特殊值代替<code>NULL</code>。</p><p>因为<code>NULL</code>类型的存储和优化都会存在性能不佳的问题，具体原因在这里就不展开了。</p><h3 id="最小数据长度" tabindex="-1">最小数据长度 <a class="header-anchor" href="#最小数据长度" aria-label="Permalink to &quot;最小数据长度&quot;">​</a></h3><p>越小的数据类型长度通常在磁盘、内存和CPU缓存中都需要更少的空间，处理起来更快</p><p>MySQL提供了6种整数类型，分别是</p><ul><li><strong>tinyint</strong></li><li><strong>smallint</strong></li><li><strong>mediumint</strong></li><li><strong>int</strong></li><li><strong>integer</strong></li><li><strong>bigint</strong></li></ul><p>不同的存储类型的最大存储范围不同，占用的存储的空间自然也不同。</p><p>例如，是否被删除的标识，建议选用<code>tinyint</code>，而不是<code>bigint</code>。</p><h3 id="使用合适的数据类型" tabindex="-1">使用合适的数据类型 <a class="header-anchor" href="#使用合适的数据类型" aria-label="Permalink to &quot;使用合适的数据类型&quot;">​</a></h3><p>原则：更小通常更好，简单就好，所有字段都得有默认值，尽量避免 NULL。</p><p>例如：数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型。(mediumint 就比 int 更合适)</p><p>比如时间字段：datetime 和 timestamp。datetime 占用8个字节，timestamp 占用4个字节，只用了一半。而 timestamp 表示的范围是 1970—2037 适合做更新时间。</p><p>MySQL可以很好的支持大数据量的存取，但是一般说来，数据库中的表越小，在它上面执行的查询也就会越快。</p><p>因此，在创建表的时候，为了获得更好的性能，我们可以将表中字段的宽度设得尽可能小。</p><p>例如：在定义邮政编码这个字段时，如果将其设置为 CHAR(255)，显然给数据库增加了不必要的空间。甚至使用VARCHAR 这种类型也是多余的，因为 CHAR(6) 就可以很好的完成任务了。</p><p>同样的，如果可以的话，我们应该使用 MEDIUMINT 而不是 BIGIN 来定义整型字段，应该尽量把字段设置为 NOT NULL，这样在将来执行查询的时候，数据库不用去比较 NULL 值。</p><p>对于某些文本字段，例如“省份”或者“性别”，我们可以将它们定义为 ENUM 类型。因为在 MySQL 中，ENUM 类型被当作数值型数据来处理，而数值型数据被处理起来的速度要比文本类型快得多。这样，我们又可以提高数据库的性能。</p><h3 id="尽量少定义-text-类型" tabindex="-1">尽量少定义 text 类型 <a class="header-anchor" href="#尽量少定义-text-类型" aria-label="Permalink to &quot;尽量少定义 text 类型&quot;">​</a></h3><p>text 类型的查询效率很低，如果必须要使用 text 定义字段，可以把此字段分离成子表，需要查询此字段时使用联合查询，这样可以提高主表的查询效率</p><h3 id="适当分表、分库策略" tabindex="-1">适当分表、分库策略 <a class="header-anchor" href="#适当分表、分库策略" aria-label="Permalink to &quot;适当分表、分库策略&quot;">​</a></h3><p>分表是指当一张表中的字段更多时，可以尝试将一张大表拆分为多张子表，把使用比较高频的主信息放入主表中，其他的放入子表，这样我们大部分查询只需要查询字段更少的主表就可以完成了，从而有效的提高了查询的效率</p><p>分库是指将一个数据库分为多个数据库。比如我们把一个数据库拆分为了多个数据库，一个主数据库用于写入和修改数据，其他的用于同步主数据并提供给客户端查询，这样就把一个库的读和写的压力，分摊给了多个库，从而提高了数据库整体的运行效率</p><h3 id="字符类型" tabindex="-1">字符类型 <a class="header-anchor" href="#字符类型" aria-label="Permalink to &quot;字符类型&quot;">​</a></h3><p>你是不是直接把所有字符串的字段都设置为<code>varchar</code>格式了？甚至怕不够，还会直接设置成<code>varchar(1024)</code>的长度？</p><p>如果不确定字段的长度，肯定是要选择<code>varchar</code>，但是<code>varchar</code>需要额外的空间来记录该字段目前占用的长度；因此如果字段的长度是固定的，尽量选用<code>char</code>，这会给你节约不少的内存空间。</p><h3 id="不要用外键、触发器和视图功能" tabindex="-1">不要用外键、触发器和视图功能 <a class="header-anchor" href="#不要用外键、触发器和视图功能" aria-label="Permalink to &quot;不要用外键、触发器和视图功能&quot;">​</a></h3><p>这也是「阿里巴巴开发手册」中提到的原则。原因有三个：</p><ol><li>降低了可读性，检查代码的同时还得查看数据库的代码；</li><li>把计算的工作交给程序，数据库只做好存储的工作，并把这件事情做好；</li><li>数据的完整性校验的工作应该由开发者完成，而不是依赖于外键，一旦用了外键，你会发现测试的时候随便删点垃圾数据都变得异常艰难。</li></ol><h3 id="图片、音频、视频存储" tabindex="-1">图片、音频、视频存储 <a class="header-anchor" href="#图片、音频、视频存储" aria-label="Permalink to &quot;图片、音频、视频存储&quot;">​</a></h3><p>不要直接存储大文件，而是要存储大文件的访问地址。</p><h3 id="大字段拆分和数据冗余" tabindex="-1">大字段拆分和数据冗余 <a class="header-anchor" href="#大字段拆分和数据冗余" aria-label="Permalink to &quot;大字段拆分和数据冗余&quot;">​</a></h3><p><strong>大字段拆分</strong>其实就是前面说过的垂直分表，把不常用的字段或者数据量较大的字段拆分出去，避免列数过多和数据量过大，尤其是习惯编写<code>SELECT *</code>的情况下，列数多和数据量大导致的问题会被严重放大！</p><p><strong>字段冗余</strong>原则上不符合数据库设计范式，但是却非常有利于快速检索。比如，合同表中存储客户id的同时可以冗余存储客户姓名，这样查询时就不需要再根据客户id获取用户姓名了。因此针对业务逻辑适当做一定程度的冗余也是一种比较好的优化技巧。</p><h2 id="常见类型选择" tabindex="-1">常见类型选择 <a class="header-anchor" href="#常见类型选择" aria-label="Permalink to &quot;常见类型选择&quot;">​</a></h2><p><strong>整数类型宽度设置</strong></p><p>MySQL可以为整数类型指定宽度，例如int(11)，实际上并没有意义，它并不会限制值的范围，对于存储和计算来说，int(1)和int(20)是相同的</p><p><strong>VARCHAR和CHAR类型</strong></p><p>char类型是定长的，而varchar存储可变字符串，比定长更省空间，但是varchar需要额外1或2个字节记录字符串长度，更新时也容易产生碎片</p><p>需要结合使用场景来选择：如果字符串列最大长度比平均长度大很多，或者列的更新很少，选择varchar较合适；如果要存很短的字符串，或者字符串值长度都相同，比如MD5值，或者列数据经常变更，选择使用char类型</p><p><strong>DATETIME和TIMESTAMP类型</strong></p><p>datetime的范围更大，能表示从1001到9999年，timestamp只能表示从1970年到2038年。datetime与时区无关，timestamp显示值依赖于时区。在大多数场景下，这两种类型都能良好地工作，但是建议使用timestamp，因为datetime占用8个字节，timestamp只占用了4个字节，timestamp空间效率更高</p><p><strong>BLOB和TEXT类型</strong></p><p>blob和text都是为存储很大数据而设计的字符串数据类型，分别采用二进制和字符方式存储</p><p>在实际使用中，要慎用这两种类型，它们的查询效率很低，如果字段必须要使用这两种类型，可以把此字段分离成子表，需要查询此字段时使用联合查询，这样可以提高主表的查询效率</p><h2 id="设计中间表" tabindex="-1">设计中间表 <a class="header-anchor" href="#设计中间表" aria-label="Permalink to &quot;设计中间表&quot;">​</a></h2><p>设计中间表，一般针对于统计分析功能，或者实时性不高的需求（报表统计，数据分析等系统）</p><h3 id="设计冗余字段" tabindex="-1">设计冗余字段 <a class="header-anchor" href="#设计冗余字段" aria-label="Permalink to &quot;设计冗余字段&quot;">​</a></h3><p>为减少关联查询，创建合理的冗余字段（创建冗余字段还需要注意数据一致性问题）。这里分库分表时较为常用。</p><h3 id="拆表" tabindex="-1">拆表 <a class="header-anchor" href="#拆表" aria-label="Permalink to &quot;拆表&quot;">​</a></h3><p>对于字段太多的大表，考虑拆表（比如一个表有100多个字段） 对于表中经常不被使用的字段或者存储数据比较多的字段，考虑拆表。</p><h3 id="主键优化" tabindex="-1">主键优化 <a class="header-anchor" href="#主键优化" aria-label="Permalink to &quot;主键优化&quot;">​</a></h3><p>每张表建议都要有一个主键（主键索引），而且<strong>主键类型最好是int类型</strong>，建议自增主键（<strong>分布式系统的情况下建议雪花算法</strong>）</p><h3 id="字段的设计" tabindex="-1">字段的设计 <a class="header-anchor" href="#字段的设计" aria-label="Permalink to &quot;字段的设计&quot;">​</a></h3><p>数据库中的表越小，在它上面执行的查询也就会越快。因此，在创建表的时候，为了获得更好的性能，我们可以将表中字段的宽度设得尽可能小。</p><ul><li><strong>使用可以存下数据最小的数据类型，合适即可</strong></li><li>尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT，如果非负则加上UNSIGNED；</li><li>VARCHAR的长度<strong>只分配真正需要的空间</strong>；</li><li>对于某些文本字段，比如**&quot;省份&quot;或者&quot;性别&quot;**，<strong>使用枚举或整数代替字符串类型</strong>；在MySQL中， ENUM类型被当作数值型数据来处理，而数值型数据被处理起来的速度要比文本类型快得多</li><li><strong>尽量使用TIMESTAMP而非DATETIME</strong>；</li><li>单表不要有太多字段，建议在20以内；</li><li>尽可能使用not null定义字段，null占用4字节空间，这样在将来执行查询的时候，数据库不用去比较NULL值。</li><li>用整型来存IP。</li><li>尽量少用 text 类型，非用不可时最好考虑拆表</li></ul><h1 id="mysql高性能优化规范建议⭐" tabindex="-1">MySQL高性能优化规范建议⭐ <a class="header-anchor" href="#mysql高性能优化规范建议⭐" aria-label="Permalink to &quot;MySQL高性能优化规范建议⭐&quot;">​</a></h1><h2 id="数据库命令规范" tabindex="-1">数据库命令规范 <a class="header-anchor" href="#数据库命令规范" aria-label="Permalink to &quot;数据库命令规范&quot;">​</a></h2><ul><li>所有数据库对象名称必须使用小写字母并用下划线分割</li><li>所有数据库对象名称禁止使用mysql保留关键字（如果表名中包含关键字查询时，需要将其用单引号括起来）</li><li>数据库对象的命名要能做到见名识意，并且最后不要超过32个字符</li><li>临时库表必须以tmp_为前缀并以日期为后缀，备份表必须以bak_为前缀并以日期(时间戳)为后缀</li><li>所有存储相同数据的列名和列类型必须一致（一般作为关联列，如果查询时关联列类型不一致会自动进行数据类型隐式转换，会造成列上的索引失效，导致查询效率降低）</li></ul><hr><h2 id="数据库基本设计规范" tabindex="-1">数据库基本设计规范 <a class="header-anchor" href="#数据库基本设计规范" aria-label="Permalink to &quot;数据库基本设计规范&quot;">​</a></h2><h3 id="_1-所有表必须使用innodb存储引擎" tabindex="-1">1. 所有表必须使用Innodb存储引擎 <a class="header-anchor" href="#_1-所有表必须使用innodb存储引擎" aria-label="Permalink to &quot;1. 所有表必须使用Innodb存储引擎&quot;">​</a></h3><p>没有特殊要求（即Innodb无法满足的功能如：列存储，存储空间数据等）的情况下，所有表必须使用Innodb存储引擎（mysql5.5之前默认使用Myisam，5.6以后默认的为Innodb）。</p><p>Innodb 支持事务，支持行级锁，更好的恢复性，高并发下性能更好。</p><h3 id="_2-数据库和表的字符集统一使用utf8" tabindex="-1">2. 数据库和表的字符集统一使用UTF8 <a class="header-anchor" href="#_2-数据库和表的字符集统一使用utf8" aria-label="Permalink to &quot;2. 数据库和表的字符集统一使用UTF8&quot;">​</a></h3><p>兼容性更好，统一字符集可以避免由于字符集转换产生的乱码，不同的字符集进行比较前需要进行转换会造成索引失效，如果数据库中有存储emoji表情的需要，字符集需要采用utf8mb4字符集。</p><h3 id="_3-所有表和字段都需要添加注释" tabindex="-1">3. 所有表和字段都需要添加注释 <a class="header-anchor" href="#_3-所有表和字段都需要添加注释" aria-label="Permalink to &quot;3. 所有表和字段都需要添加注释&quot;">​</a></h3><p>使用comment从句添加表和列的备注，从一开始就进行数据字典的维护</p><h3 id="_4-尽量控制单表数据量的大小-建议控制在500万以内" tabindex="-1">4. 尽量控制单表数据量的大小，建议控制在500万以内 <a class="header-anchor" href="#_4-尽量控制单表数据量的大小-建议控制在500万以内" aria-label="Permalink to &quot;4. 尽量控制单表数据量的大小，建议控制在500万以内&quot;">​</a></h3><p>500万并不是Mysql数据库的限制，过大会造成修改表结构，备份，恢复都会有很大的问题。</p><p>可以用历史数据归档（应用于日志数据），分库分表（应用于业务数据）等手段来控制数据量大小</p><h3 id="_5-谨慎使用mysql分区表" tabindex="-1">5. 谨慎使用MySQL分区表 <a class="header-anchor" href="#_5-谨慎使用mysql分区表" aria-label="Permalink to &quot;5. 谨慎使用MySQL分区表&quot;">​</a></h3><p>分区表在物理上表现为多个文件，在逻辑上表现为一个表；</p><p>谨慎选择分区键，跨分区查询效率可能更低；</p><p>建议采用物理分表的方式管理大数据。</p><h3 id="_6-尽量做到冷热数据分离-减小表的宽度" tabindex="-1">6. 尽量做到冷热数据分离，减小表的宽度 <a class="header-anchor" href="#_6-尽量做到冷热数据分离-减小表的宽度" aria-label="Permalink to &quot;6. 尽量做到冷热数据分离，减小表的宽度&quot;">​</a></h3><blockquote><p>MySQL限制每个表最多存储4096列，并且每一行数据的大小不能超过65535字节。</p></blockquote><p>减少磁盘IO,保证热数据的内存缓存命中率（表越宽，把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO）；</p><p>更有效的利用缓存，避免读入无用的冷数据；</p><p>经常一起使用的列放到一个表中（避免更多的关联操作）。</p><h3 id="_7-禁止在表中建立预留字段" tabindex="-1">7. 禁止在表中建立预留字段 <a class="header-anchor" href="#_7-禁止在表中建立预留字段" aria-label="Permalink to &quot;7. 禁止在表中建立预留字段&quot;">​</a></h3><p>预留字段的命名很难做到见名识义。</p><p>预留字段无法确认存储的数据类型，所以无法选择合适的类型。</p><p>对预留字段类型的修改，会对表进行锁定。</p><h3 id="_8-禁止在数据库中存储图片-文件等大的二进制数据" tabindex="-1">8. 禁止在数据库中存储图片，文件等大的二进制数据 <a class="header-anchor" href="#_8-禁止在数据库中存储图片-文件等大的二进制数据" aria-label="Permalink to &quot;8. 禁止在数据库中存储图片，文件等大的二进制数据&quot;">​</a></h3><p>通常文件很大，会短时间内造成数据量快速增长，数据库进行数据库读取时，通常会进行大量的随机IO操作，文件很大时，IO操作很耗时。</p><p>通常存储于文件服务器，数据库只存储文件地址信息</p><h3 id="_9-禁止在线上做数据库压力测试" tabindex="-1">9. 禁止在线上做数据库压力测试 <a class="header-anchor" href="#_9-禁止在线上做数据库压力测试" aria-label="Permalink to &quot;9. 禁止在线上做数据库压力测试&quot;">​</a></h3><h3 id="_10-禁止从开发环境测试环境直接连接生产环境数据库" tabindex="-1">10. 禁止从开发环境测试环境直接连接生产环境数据库 <a class="header-anchor" href="#_10-禁止从开发环境测试环境直接连接生产环境数据库" aria-label="Permalink to &quot;10. 禁止从开发环境测试环境直接连接生产环境数据库&quot;">​</a></h3><hr><h2 id="数据库字段设计规范" tabindex="-1">数据库字段设计规范 <a class="header-anchor" href="#数据库字段设计规范" aria-label="Permalink to &quot;数据库字段设计规范&quot;">​</a></h2><h3 id="_1-优先选择符合存储需要的最小的数据类型" tabindex="-1">1. 优先选择符合存储需要的最小的数据类型 <a class="header-anchor" href="#_1-优先选择符合存储需要的最小的数据类型" aria-label="Permalink to &quot;1. 优先选择符合存储需要的最小的数据类型&quot;">​</a></h3><p><strong>原因：</strong></p><p>列的字段越大，建立索引时所需要的空间也就越大，这样一页中所能存储的索引节点的数量也就越少也越少，在遍历时所需要的IO次数也就越多，索引的性能也就越差。</p><p><strong>方法：</strong></p><h4 id="_1、将字符串转换成数字类型存储-如-将ip地址转换成整形数据" tabindex="-1">1、将字符串转换成数字类型存储，如：将IP地址转换成整形数据 <a class="header-anchor" href="#_1、将字符串转换成数字类型存储-如-将ip地址转换成整形数据" aria-label="Permalink to &quot;1、将字符串转换成数字类型存储，如：将IP地址转换成整形数据&quot;">​</a></h4><p>mysql提供了两个方法来处理ip地址</p><ul><li>inet_aton 把ip转为无符号整型(4-8位)</li><li>inet_ntoa 把整型的ip转为地址</li></ul><p>插入数据前，先用inet_aton把ip地址转为整型，可以节省空间，显示数据时，使用inet_ntoa把整型的ip地址转为地址显示即可。</p><h4 id="_2、对于非负型的数据-如自增id、整型ip-来说-要优先使用无符号整型来存储" tabindex="-1">2、对于非负型的数据（如自增ID、整型IP）来说，要优先使用无符号整型来存储 <a class="header-anchor" href="#_2、对于非负型的数据-如自增id、整型ip-来说-要优先使用无符号整型来存储" aria-label="Permalink to &quot;2、对于非负型的数据（如自增ID、整型IP）来说，要优先使用无符号整型来存储&quot;">​</a></h4><p><strong>原因：</strong></p><p>无符号相对于有符号可以多出一倍的存储空间</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">SIGNED INT </span><span style="color:#89DDFF;">-</span><span style="color:#F78C6C;">2147483648</span><span style="color:#89DDFF;">~</span><span style="color:#F78C6C;">2147483647</span></span>
<span class="line"><span style="color:#A6ACCD;">UNSIGNED INT </span><span style="color:#F78C6C;">0</span><span style="color:#89DDFF;">~</span><span style="color:#F78C6C;">4294967295</span></span></code></pre></div><p>VARCHAR(N)中的N代表的是字符数，而不是字节数，使用UTF8存储255个汉字 Varchar(255)=765个字节。<strong>过大的长度会消耗更多的内存。</strong></p><h3 id="_2-避免使用text、blob数据类型-最常见的text类型可以存储64k的数据" tabindex="-1">2. 避免使用TEXT、BLOB数据类型，最常见的TEXT类型可以存储64k的数据 <a class="header-anchor" href="#_2-避免使用text、blob数据类型-最常见的text类型可以存储64k的数据" aria-label="Permalink to &quot;2. 避免使用TEXT、BLOB数据类型，最常见的TEXT类型可以存储64k的数据&quot;">​</a></h3><h5 id="_1、建议把blob或是text列分离到单独的扩展表中" tabindex="-1"><strong>1、建议把BLOB或是TEXT列分离到单独的扩展表中</strong> <a class="header-anchor" href="#_1、建议把blob或是text列分离到单独的扩展表中" aria-label="Permalink to &quot;**1、建议把BLOB或是TEXT列分离到单独的扩展表中**&quot;">​</a></h5><p>Mysql内存临时表不支持TEXT、BLOB这样的大数据类型，如果查询中包含这样的数据，在排序等操作时，就不能使用内存临时表，必须使用磁盘临时表进行。而且对于这种数据，Mysql还是要进行二次查询，会使sql性能变得很差，但是不是说一定不能使用这样的数据类型。</p><p>如果一定要使用，建议把BLOB或是TEXT列分离到单独的扩展表中，查询时一定不要使用select * 而只需要取出必要的列，不需要TEXT列的数据时不要对该列进行查询。</p><h5 id="_2、text或blob类型只能使用前缀索引" tabindex="-1"><strong>2、TEXT或BLOB类型只能使用前缀索引</strong> <a class="header-anchor" href="#_2、text或blob类型只能使用前缀索引" aria-label="Permalink to &quot;**2、TEXT或BLOB类型只能使用前缀索引**&quot;">​</a></h5><p>因为<a href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247487885&amp;idx=1&amp;sn=65b1bf5f7d4505502620179669a9c2df&amp;chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">MySQL</a>对索引字段长度是有限制的，所以TEXT类型只能使用前缀索引，并且TEXT列上是不能有默认值的</p><h3 id="_3-避免使用enum类型" tabindex="-1">3. 避免使用ENUM类型 <a class="header-anchor" href="#_3-避免使用enum类型" aria-label="Permalink to &quot;3. 避免使用ENUM类型&quot;">​</a></h3><p>修改ENUM值需要使用ALTER语句</p><p>ENUM类型的ORDER BY操作效率低，需要额外操作</p><p>禁止使用数值作为ENUM的枚举值</p><h3 id="_4-尽可能把所有列定义为not-null" tabindex="-1">4. 尽可能把所有列定义为NOT NULL <a class="header-anchor" href="#_4-尽可能把所有列定义为not-null" aria-label="Permalink to &quot;4. 尽可能把所有列定义为NOT NULL&quot;">​</a></h3><p><strong>原因：</strong></p><p>索引NULL列需要额外的空间来保存，所以要占用更多的空间</p><p>进行比较和计算时要对NULL值做特别的处理</p><h3 id="_5-使用datetime类型-8个字节-存储时间" tabindex="-1">5. 使用DATETIME类型（8个字节）存储时间 <a class="header-anchor" href="#_5-使用datetime类型-8个字节-存储时间" aria-label="Permalink to &quot;5. 使用DATETIME类型（8个字节）存储时间&quot;">​</a></h3><p>TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07</p><p>TIMESTAMP 占用4字节和INT相同，但比INT可读性高</p><p>超出TIMESTAMP取值范围的使用DATETIME类型存储</p><p><strong>经常会有人用字符串存储日期型的数据（不正确的做法）</strong></p><ul><li>缺点1：无法用日期函数进行计算和比较</li><li>缺点2：用字符串存储日期要占用更多的空间</li></ul><h3 id="_6-同财务相关的金额类数据必须使用decimal类型" tabindex="-1">6. 同财务相关的金额类数据必须使用decimal类型 <a class="header-anchor" href="#_6-同财务相关的金额类数据必须使用decimal类型" aria-label="Permalink to &quot;6. 同财务相关的金额类数据必须使用decimal类型&quot;">​</a></h3><ul><li>非精准浮点：float,double</li><li>精准浮点：decimal</li></ul><p>Decimal类型为精准浮点数，在计算时不会丢失精度</p><p>占用空间由定义的宽度决定，每4个字节可以存储9位数字，并且小数点要占用一个字节</p><p>可用于存储比bigint更大的整型数据</p><hr><h2 id="索引设计规范" tabindex="-1">索引设计规范 <a class="header-anchor" href="#索引设计规范" aria-label="Permalink to &quot;索引设计规范&quot;">​</a></h2><h3 id="_1-限制表的索引数量-建议单张表索引不超过5个" tabindex="-1">1. 限制表的索引数量，建议单张表索引不超过5个 <a class="header-anchor" href="#_1-限制表的索引数量-建议单张表索引不超过5个" aria-label="Permalink to &quot;1. 限制表的索引数量，建议单张表索引不超过5个&quot;">​</a></h3><p>索引并不是越多越好！索引可以提高效率同样可以降低效率。</p><p>索引可以增加查询效率，但同样也会降低插入和更新的效率，甚至有些情况下会降低查询效率。</p><p>因为mysql优化器在选择如何优化查询时，会根据统一信息，对每一个可以用到的索引来进行评估，以生成出一个最好的执行计划，如果同时有很多个索引都可以用于查询，就会增加mysql优化器生成执行计划的时间，同样会降低查询性能。</p><h3 id="_2-禁止给表中的每一列都建立单独的索引" tabindex="-1">2. 禁止给表中的每一列都建立单独的索引 <a class="header-anchor" href="#_2-禁止给表中的每一列都建立单独的索引" aria-label="Permalink to &quot;2. 禁止给表中的每一列都建立单独的索引&quot;">​</a></h3><p>5.6版本之前，一个sql只能使用到一个表中的一个索引，5.6以后，虽然有了合并索引的优化方式，但是还是远远没有使用一个联合索引的查询方式好。</p><h3 id="_3-每个innodb表必须有个主键" tabindex="-1">3. 每个Innodb表必须有个主键 <a class="header-anchor" href="#_3-每个innodb表必须有个主键" aria-label="Permalink to &quot;3. 每个Innodb表必须有个主键&quot;">​</a></h3><p>Innodb是一种索引组织表：数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引，但是表的存储顺序只能有一种。</p><p>Innodb是按照主键索引的顺序来组织表的</p><ul><li>不要使用更新频繁的列作为主键，不适用多列主键（相当于联合索引）</li><li>不要使用UUID,MD5,HASH,字符串列作为主键（无法保证数据的顺序增长）</li><li>主键建议使用自增ID值</li></ul><hr><h2 id="常见索引列建议" tabindex="-1">常见索引列建议 <a class="header-anchor" href="#常见索引列建议" aria-label="Permalink to &quot;常见索引列建议&quot;">​</a></h2><ul><li>出现在SELECT、UPDATE、DELETE语句的WHERE从句中的列</li><li>包含在ORDER BY、GROUP BY、DISTINCT中的字段</li><li>并不要将符合1和2中的字段的列都建立一个索引， 通常将1、2中的字段建立联合索引效果更好</li><li>多表join的关联列</li></ul><hr><h2 id="如何选择索引列的顺序" tabindex="-1">如何选择索引列的顺序 <a class="header-anchor" href="#如何选择索引列的顺序" aria-label="Permalink to &quot;如何选择索引列的顺序&quot;">​</a></h2><p>建立<a href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247485618&amp;idx=1&amp;sn=ed892f572b81e6aa6a7be5c43c2e7351&amp;chksm=ebd6379edca1be88f7d3703db31115ced7c5cacd814b684731d6bc1f66f547f341b6c6451da4&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">索引</a>的目的是：希望通过索引进行数据查找，减少随机IO，增加查询性能 ，索引能过滤出越少的数据，则从磁盘中读入的数据也就越少。</p><ul><li>区分度最高的放在联合索引的最左侧（区分度=列中不同值的数量/列的总行数）</li><li>尽量把字段长度小的列放在联合索引的最左侧（因为字段长度越小，一页能存储的数据量越大，IO性能也就越好）</li><li>使用最频繁的列放到联合索引的左侧（这样可以比较少的建立一些索引）</li></ul><hr><h2 id="避免建立冗余索引和重复索引-增加了查询优化器生成执行计划的时间" tabindex="-1">避免建立冗余索引和重复索引（增加了查询优化器生成执行计划的时间） <a class="header-anchor" href="#避免建立冗余索引和重复索引-增加了查询优化器生成执行计划的时间" aria-label="Permalink to &quot;避免建立冗余索引和重复索引（增加了查询优化器生成执行计划的时间）&quot;">​</a></h2><ul><li>重复索引示例：primary key(id)、index(id)、unique index(id)</li><li>冗余索引示例：index(a,b,c)、index(a,b)、index(a)</li></ul><hr><h2 id="对于频繁的查询优先考虑使用覆盖索引" tabindex="-1">对于频繁的查询优先考虑使用覆盖索引 <a class="header-anchor" href="#对于频繁的查询优先考虑使用覆盖索引" aria-label="Permalink to &quot;对于频繁的查询优先考虑使用覆盖索引&quot;">​</a></h2><blockquote><p>覆盖索引：就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引</p></blockquote><p><strong>覆盖索引的好处</strong></p><h3 id="避免innodb表进行索引的二次查询" tabindex="-1">避免Innodb表进行索引的二次查询 <a class="header-anchor" href="#避免innodb表进行索引的二次查询" aria-label="Permalink to &quot;避免Innodb表进行索引的二次查询&quot;">​</a></h3><p>Innodb是以聚集索引的顺序来存储的，对于Innodb来说，二级索引在叶子节点中所保存的是行的主键信息，如果是用二级索引查询数据的话，在查找到相应的键值后，还要通过主键进行二次查询才能获取我们真实所需要的数据。</p><p>而在覆盖索引中，二级索引的键值中可以获取所有的数据，避免了对主键的二次查询 ，减少了IO操作，提升了查询效率。</p><h3 id="可以把随机io变成顺序io加快查询效率" tabindex="-1">可以把随机IO变成顺序IO加快查询效率 <a class="header-anchor" href="#可以把随机io变成顺序io加快查询效率" aria-label="Permalink to &quot;可以把随机IO变成顺序IO加快查询效率&quot;">​</a></h3><p>由于覆盖索引是按键值的顺序存储的，对于IO密集型的范围查找来说，对比随机从磁盘读取每一行的数据IO要少的多，因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO。</p><hr><h2 id="索引set规范" tabindex="-1">索引SET规范 <a class="header-anchor" href="#索引set规范" aria-label="Permalink to &quot;索引SET规范&quot;">​</a></h2><h4 id="尽量避免使用外键约束" tabindex="-1">尽量避免使用外键约束 <a class="header-anchor" href="#尽量避免使用外键约束" aria-label="Permalink to &quot;尽量避免使用外键约束&quot;">​</a></h4><ul><li>不建议使用外键约束（foreign key），但一定要在表与表之间的关联键上建立索引</li><li>外键可用于保证数据的参照完整性，但建议在业务端实现</li><li>外键会影响父表和子表的写操作从而降低性能</li></ul><hr><h2 id="数据库sql开发规范" tabindex="-1">数据库SQL开发规范 <a class="header-anchor" href="#数据库sql开发规范" aria-label="Permalink to &quot;数据库SQL开发规范&quot;">​</a></h2><h3 id="_1-建议使用预编译语句进行数据库操作" tabindex="-1">1. 建议使用预编译语句进行数据库操作 <a class="header-anchor" href="#_1-建议使用预编译语句进行数据库操作" aria-label="Permalink to &quot;1. 建议使用预编译语句进行数据库操作&quot;">​</a></h3><p>预编译语句可以重复使用这些计划，减少SQL编译所需要的时间，还可以解决动态SQL所带来的SQL注入的问题。</p><p>只传参数，比传递SQL语句更高效。</p><p>相同语句可以一次解析，多次使用，提高处理效率。</p><h3 id="_2-避免数据类型的隐式转换" tabindex="-1">2. 避免数据类型的隐式转换 <a class="header-anchor" href="#_2-避免数据类型的隐式转换" aria-label="Permalink to &quot;2. 避免数据类型的隐式转换&quot;">​</a></h3><p>隐式转换会导致索引失效如:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,phone </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> customer </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">111</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h3 id="_3-充分利用表上已经存在的索引" tabindex="-1">3. 充分利用表上已经存在的索引 <a class="header-anchor" href="#_3-充分利用表上已经存在的索引" aria-label="Permalink to &quot;3. 充分利用表上已经存在的索引&quot;">​</a></h3><p>避免使用双%号的查询条件。如：<code>a like &#39;%123%&#39;</code>，（如果无前置%,只有后置%，是可以用到列上的索引的）</p><p>一个SQL只能利用到复合索引中的一列进行范围查询。如：有 a,b,c列的联合索引，在查询条件中有a列的范围查询，则在b,c列上的索引将不会被用到。</p><p>在定义联合索引时，如果a列要用到范围查找的话，就要把a列放到联合索引的右侧，使用left join 或 not exists 来优化not in 操作，因为not in 也通常会使用索引失效。</p><h3 id="_4-数据库设计时-应该要对以后扩展进行考虑" tabindex="-1">4. 数据库设计时，应该要对以后扩展进行考虑 <a class="header-anchor" href="#_4-数据库设计时-应该要对以后扩展进行考虑" aria-label="Permalink to &quot;4. 数据库设计时，应该要对以后扩展进行考虑&quot;">​</a></h3><h3 id="_5-程序连接不同的数据库使用不同的账号-禁止跨库查询" tabindex="-1">5. 程序连接不同的数据库使用不同的账号，禁止跨库查询 <a class="header-anchor" href="#_5-程序连接不同的数据库使用不同的账号-禁止跨库查询" aria-label="Permalink to &quot;5. 程序连接不同的数据库使用不同的账号，禁止跨库查询&quot;">​</a></h3><ul><li>为数据库迁移和分库分表留出余地</li><li>降低业务耦合度</li><li>避免权限过大而产生的安全风险</li></ul><h3 id="_6-禁止使用select-必须使用select-字段列表" tabindex="-1">6. 禁止使用SELECT * 必须使用SELECT &lt;字段列表&gt; <a class="header-anchor" href="#_6-禁止使用select-必须使用select-字段列表" aria-label="Permalink to &quot;6. 禁止使用SELECT * 必须使用SELECT &lt;字段列表&gt;&quot;">​</a></h3><p><strong>原因：</strong></p><ul><li>消耗更多的CPU和IO以网络带宽资源</li><li>无法使用覆盖索引</li><li>可减少表结构变更带来的影响</li></ul><h3 id="_7-禁止使用不含字段列表的insert语句" tabindex="-1">7. 禁止使用不含字段列表的INSERT语句 <a class="header-anchor" href="#_7-禁止使用不含字段列表的insert语句" aria-label="Permalink to &quot;7. 禁止使用不含字段列表的INSERT语句&quot;">​</a></h3><p>如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">a</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">b</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">c</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>应使用：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> t(c1,c2,c3) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">a</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">b</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">c</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><h3 id="_8-避免使用子查询-可以把子查询优化为join操作" tabindex="-1">8. 避免使用子查询，可以把子查询优化为join操作 <a class="header-anchor" href="#_8-避免使用子查询-可以把子查询优化为join操作" aria-label="Permalink to &quot;8. 避免使用子查询，可以把子查询优化为join操作&quot;">​</a></h3><p>通常子查询在in子句中，且子查询中为简单SQL(不包含union、group by、order by、limit从句)时,才可以把子查询转化为关联查询进行优化。</p><p><strong>子查询性能差的原因：</strong></p><p>子查询的结果集无法使用索引，通常子查询的结果集会被存储到临时表中，不论是内存临时表还是磁盘临时表都不会存在索引，所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询，其对查询性能的影响也就越大。</p><p>由于子查询会产生大量的临时表也没有索引，所以会消耗过多的CPU和IO资源，产生大量的慢查询。</p><h3 id="_9-避免使用join关联太多的表" tabindex="-1">9. 避免使用JOIN关联太多的表 <a class="header-anchor" href="#_9-避免使用join关联太多的表" aria-label="Permalink to &quot;9. 避免使用JOIN关联太多的表&quot;">​</a></h3><p>对于Mysql来说，是存在关联缓存的，缓存的大小可以由join_buffer_size参数进行设置。</p><p>在Mysql中，对于同一个SQL多关联（join）一个表，就会多分配一个关联缓存，如果在一个SQL中关联的表越多，所占用的内存也就越大。</p><p>如果程序中大量的使用了多表关联的操作，同时join_buffer_size设置的也不合理的情况下，就容易造成服务器内存溢出的情况，就会影响到服务器数据库性能的稳定性。</p><p>同时对于关联操作来说，会产生临时表操作，影响查询效率，Mysql最多允许关联61个表，建议不超过5个。</p><h3 id="_10-减少同数据库的交互次数" tabindex="-1">10. 减少同数据库的交互次数 <a class="header-anchor" href="#_10-减少同数据库的交互次数" aria-label="Permalink to &quot;10. 减少同数据库的交互次数&quot;">​</a></h3><p>数据库更适合处理批量操作，合并多个相同的操作到一起，可以提高处理效率。</p><h3 id="_11-对应同一列进行or判断时-使用in代替or" tabindex="-1">11. 对应同一列进行or判断时，使用in代替or <a class="header-anchor" href="#_11-对应同一列进行or判断时-使用in代替or" aria-label="Permalink to &quot;11. 对应同一列进行or判断时，使用in代替or&quot;">​</a></h3><p>in 的值不要超过500个，in 操作可以更有效的利用索引，or大多数情况下很少能利用到索引。</p><h3 id="_12-禁止使用order-by-rand-进行随机排序" tabindex="-1">12. 禁止使用order by rand() 进行随机排序 <a class="header-anchor" href="#_12-禁止使用order-by-rand-进行随机排序" aria-label="Permalink to &quot;12. 禁止使用order by rand() 进行随机排序&quot;">​</a></h3><p>order by rand()会把表中所有符合条件的数据装载到内存中，然后在内存中对所有数据根据随机生成的值进行排序，并且可能会对每一行都生成一个随机值，如果满足条件的数据集非常大，就会消耗大量的CPU和IO及内存资源。</p><p>推荐在程序中获取一个随机值，然后从数据库中获取数据的方式。</p><h3 id="_13-where从句中禁止对列进行函数转换和计算" tabindex="-1">13. WHERE从句中禁止对列进行函数转换和计算 <a class="header-anchor" href="#_13-where从句中禁止对列进行函数转换和计算" aria-label="Permalink to &quot;13. WHERE从句中禁止对列进行函数转换和计算&quot;">​</a></h3><p>对列进行函数转换或计算时会导致无法使用索引</p><p><strong>不推荐：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">date</span><span style="color:#A6ACCD;">(create_time)</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">20190101</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><p><strong>推荐：</strong></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> create_time </span><span style="color:#89DDFF;">&gt;=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">20190101</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> create_time </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">20190102</span><span style="color:#89DDFF;">&#39;</span></span></code></pre></div><h3 id="_14-在不会有重复值时使用union-all-而不是union" tabindex="-1">14. 在不会有重复值时使用UNION ALL 而不是UNION <a class="header-anchor" href="#_14-在不会有重复值时使用union-all-而不是union" aria-label="Permalink to &quot;14. 在不会有重复值时使用UNION ALL 而不是UNION&quot;">​</a></h3><ul><li>UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作</li><li>UNION ALL 不会再对结果集进行去重操作</li></ul><h3 id="_15-拆分复杂的大sql为多个小sql" tabindex="-1">15. 拆分复杂的大SQL为多个小SQL <a class="header-anchor" href="#_15-拆分复杂的大sql为多个小sql" aria-label="Permalink to &quot;15. 拆分复杂的大SQL为多个小SQL&quot;">​</a></h3><ul><li>大SQL逻辑上比较复杂，需要占用大量CPU进行计算的SQL</li><li>MySQL中，一个SQL只能使用一个CPU进行计算</li><li>SQL拆分后可以通过并行执行来提高处理效率</li></ul><hr><h2 id="数据库操作行为规范" tabindex="-1">数据库操作行为规范 <a class="header-anchor" href="#数据库操作行为规范" aria-label="Permalink to &quot;数据库操作行为规范&quot;">​</a></h2><h3 id="超100万行的批量写-update、delete、insert-操作-要分批多次进行操作" tabindex="-1">超100万行的批量写（UPDATE、DELETE、INSERT）操作，要分批多次进行操作 <a class="header-anchor" href="#超100万行的批量写-update、delete、insert-操作-要分批多次进行操作" aria-label="Permalink to &quot;超100万行的批量写（UPDATE、DELETE、INSERT）操作，要分批多次进行操作&quot;">​</a></h3><h4 id="_1、大批量操作可能会造成严重的主从延迟" tabindex="-1">1、大批量操作可能会造成严重的主从延迟 <a class="header-anchor" href="#_1、大批量操作可能会造成严重的主从延迟" aria-label="Permalink to &quot;1、大批量操作可能会造成严重的主从延迟&quot;">​</a></h4><p>主从环境中,大批量操作可能会造成严重的主从延迟，大批量的写操作一般都需要执行一定长的时间， 而只有当主库上执行完成后，才会在其他从库上执行，所以会造成主库与从库长时间的延迟情况</p><h4 id="_2、binlog日志为row格式时会产生大量的日志" tabindex="-1">2、binlog日志为row格式时会产生大量的日志 <a class="header-anchor" href="#_2、binlog日志为row格式时会产生大量的日志" aria-label="Permalink to &quot;2、binlog日志为row格式时会产生大量的日志&quot;">​</a></h4><p>大批量写操作会产生大量日志，特别是对于row格式二进制数据而言，由于在row格式中会记录每一行数据的修改，我们一次修改的数据越多，产生的日志量也就会越多，日志的传输和恢复所需要的时间也就越长，这也是造成主从延迟的一个原因</p><h4 id="_3、避免产生大事务操作" tabindex="-1">3、避免产生大事务操作 <a class="header-anchor" href="#_3、避免产生大事务操作" aria-label="Permalink to &quot;3、避免产生大事务操作&quot;">​</a></h4><p>大批量修改数据，一定是在一个事务中进行的，这就会造成表中大批量数据进行锁定，从而导致大量的阻塞，阻塞会对MySQL的性能产生非常大的影响。</p><p>特别是长时间的阻塞会占满所有数据库的可用连接，这会使生产环境中的其他应用无法连接到数据库，因此一定要注意大批量写操作要进行分批</p><h3 id="对于大表使用pt-online-schema-change修改表结构" tabindex="-1">对于大表使用pt-online-schema-change修改表结构 <a class="header-anchor" href="#对于大表使用pt-online-schema-change修改表结构" aria-label="Permalink to &quot;对于大表使用pt-online-schema-change修改表结构&quot;">​</a></h3><ul><li>避免大表修改产生的主从延迟</li><li>避免在对表字段进行修改时进行锁表</li></ul><p>对大表数据结构的修改一定要谨慎，会造成严重的锁表操作，尤其是生产环境，是不能容忍的。</p><p>pt-online-schema-change它会首先建立一个与原表结构相同的新表，并且在新表上进行表结构的修改，然后再把原表中的数据复制到新表中，并在原表中增加一些触发器。把原表中新增的数据也复制到新表中，在行所有数据复制完成之后，把新表命名成原表，并把原来的表删除掉。把原来一个DDL操作，分解成多个小的批次进行。</p><h3 id="禁止为程序使用的账号赋予super权限" tabindex="-1">禁止为程序使用的账号赋予super权限 <a class="header-anchor" href="#禁止为程序使用的账号赋予super权限" aria-label="Permalink to &quot;禁止为程序使用的账号赋予super权限&quot;">​</a></h3><ul><li>当达到最大连接数限制时，还运行1个有super权限的用户连接</li><li>super权限只能留给DBA处理问题的账号使用</li></ul><h3 id="对于程序连接数据库账号-遵循权限最小原则" tabindex="-1">对于程序连接数据库账号，遵循权限最小原则 <a class="header-anchor" href="#对于程序连接数据库账号-遵循权限最小原则" aria-label="Permalink to &quot;对于程序连接数据库账号，遵循权限最小原则&quot;">​</a></h3><ul><li>程序使用数据库账号只能在一个DB下使用，不准跨库</li><li>程序使用的账号原则上不准有drop权限</li></ul><h1 id="mysql莫名其妙的断开连接以及解决方案" tabindex="-1">MySQL莫名其妙的断开连接以及解决方案 <a class="header-anchor" href="#mysql莫名其妙的断开连接以及解决方案" aria-label="Permalink to &quot;MySQL莫名其妙的断开连接以及解决方案&quot;">​</a></h1><h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>最近遇到在将本地的项目部署到服务器上之后遇到的一个奇怪问题</p><p>在部署完成后，网站当时可以正常工作，但是第二天访问网站的时候却会遇到一个500 Server Error。</p><p><strong>从日志中可以看出是MySQL数据库出现了异常</strong></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208181628366.png" alt="image-20220818162823267" style="zoom:67%;"><p>翻译如下:</p><blockquote><p>最后一个数据包在 83827560 ms 之前被成功接收，最后一个数据包在83827560 ms 之前被成功发送。比服务的配置参数<code>wait_timeout</code>的值要长。</p></blockquote><p><strong>日志中给出的建议如下</strong></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208181628150.png" alt="image-20220818162846044" style="zoom:67%;"><p>翻译如下：</p><blockquote><p>你应考虑在程序中进行数据库操作之前检验数据库连接的有效性或者将数据库的autoReconnect属性设置为true来避免这个问题</p></blockquote><p>关于<code>wait_timeout</code>和autoReconnect下面我们会依次分析介绍！</p><h2 id="原因分析" tabindex="-1">原因分析 <a class="header-anchor" href="#原因分析" aria-label="Permalink to &quot;原因分析&quot;">​</a></h2><p>我们进入mysql的命令行查询超时时间</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208181629293.png" alt="image-20220818162902141" style="zoom:67%;"><p>28800单位是秒转化成小时就是8小时</p><p>看出MySQL的默认设置，当一个连接的空闲时间超过8小时后，MySQL就会断开该连接</p><p>所以发现问题出在如果超过这个<code>wait_timeout</code>时间(默认是8小时)对数据库没有任何操作，那么MySQL会自动关闭数据库连接以节省资源</p><p>数据库连接自动断开的问题确实是在第二天发生了，也就是在一个晚上没有对数据库进行操作(显然超过了8小时)的情况下发生的这个问题</p><p><strong>大家用命令show processlist; 可以查看Sleep状态的进程Sleep，同时可以看到每个进程Sleep多久了：</strong></p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208181631417.png" alt="image-20220818163155246" style="zoom:67%;"><p>下面介绍下解决和优化办法！</p><h2 id="解决方法⭐" tabindex="-1">解决方法⭐ <a class="header-anchor" href="#解决方法⭐" aria-label="Permalink to &quot;解决方法⭐&quot;">​</a></h2><h3 id="_1-autoreconnect" tabindex="-1">1.autoReconnect <a class="header-anchor" href="#_1-autoreconnect" aria-label="Permalink to &quot;1.autoReconnect&quot;">​</a></h3><p>这个参数表示在mysql超时断开连接后会自动重新连接</p><p>配置的话，只需要在连接mysql的语句写上autoReconnect=true</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">jdbc</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;">mysql:</span><span style="color:#89DDFF;">//</span><span style="color:#F78C6C;">127.0.0.1</span><span style="color:#A6ACCD;">:</span><span style="color:#F78C6C;">3306</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">stock_tweet</span><span style="color:#89DDFF;">?</span><span style="color:#A6ACCD;">autoReconnect</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">true</span></span></code></pre></div><p><strong>下面是MySQL官网对autoReconnect的解释:</strong></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/hC3oNAJqSRzsflmFTIzdIic3ljut36p4uVyhNqqX3UeH0y1A0icA6a8XbLYb0l1vAblqnrotHHY6KE9WEQbfjx1g/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>同时可以看到官网不推荐使用这个参数，因为它有一些副作用</p><p>具体介绍下：</p><ul><li>原有连接上的事务将会被回滚，事务的提交模式将会丢失</li><li>原有连接持有的表的锁将会全部释放</li><li>原有连接关联的会话Session将会丢失，重新恢复的连接关联的将会是一个新的会话Session</li><li>原有连接定义的用户变量将会丢失</li><li>原有连接定义的预编译SQL将会丢失</li><li>原有连接失效，新的连接恢复后，MySQL将会使用新的记录行来存储连接中的性能数据</li></ul><h3 id="_2-修改配置" tabindex="-1">2.修改配置 <a class="header-anchor" href="#_2-修改配置" aria-label="Permalink to &quot;2.修改配置&quot;">​</a></h3><p>涉及到两个配置参数<code>interactive_timeout和wait_timeout</code></p><p><code>wait_timeout </code>指的是mysql在关闭一个非交互的连接之前所要等待的秒数</p><p><code>interactive_time</code> 指的是mysql在关闭一个交互的连接之前所要等待的秒数</p><p>对于交互和非交互连接，说得直白一点就是，通过mysql客户端连接数据库是交互式连接，通过jdbc连接数据库是非交互式连接。</p><p>配置方法：</p><blockquote><p>1.会话方式</p></blockquote><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">msyql&gt; set global wait_timeout=2880000;</span></span>
<span class="line"><span style="color:#A6ACCD;">msyql&gt; set global interactive_timeout=2880000;</span></span></code></pre></div><p>这种方式只对当前会话生效</p><blockquote><p>2.修改配置文件方式</p></blockquote><p>修改/etc/my.cnf文件，在 [mysqld] 节中设置：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208181632244.png" alt="image-20220818163258101" style="zoom:50%;"><p>之后再重启下服务器就好了</p><p><strong>注意：</strong></p><p>将<code>wait_timeout</code>这个值设置得大了，可能会导致空闲连接过多。</p><p>如果你的MySQL Server有大量的闲置连接，他们不仅会白白消耗内存，而且如果连接一直在累加而不断开，最终肯定会达到MySQL Server的连接上限数，这会报&#39;too many connections&#39;的错误。</p><h3 id="连接池配置" tabindex="-1">连接池配置 <a class="header-anchor" href="#连接池配置" aria-label="Permalink to &quot;连接池配置&quot;">​</a></h3><p>因为连接池的配置也会影响项目和MySQL的连接，所以也需要对数据库连接池的一些配置做一定修改</p><p>我们以Spring Boot 2.0默认的数据库连接池HikariCP为例</p><p>主要是下面这几个配置</p><p><strong>maximum-pool-size：</strong></p><p>最大连接数，超过这个数，新的数据库访问线程会被阻，缺省值：10。</p><p>常见的错误是设置一个太大的值，连接数多反而性能下降。</p><p>参考计算公式是：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">#</span><span style="color:#A6ACCD;"> core_count：CPU个数，effective_spindle_count:硬盘个数</span></span>
<span class="line"><span style="color:#A6ACCD;">connections </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">(core_count </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> effective_spindle_count)</span></span></code></pre></div><p>例如：一个4核，1块硬盘的服务器，连接数 = （4 * 2） + 1 = 9，凑个整数，10就可以了。</p><p><strong>minimum-idle：</strong></p><p>最小的连接数目</p><p><strong>max-lifetime:</strong></p><p>最大的连接时间，用来设置一个connection在连接池中的存活时间</p><p>缺省：30分钟。强烈建议设置比数据库超时时长少一点（MySQL的<code>wait_timeout</code>参数一般为8小时）。</p><p><strong>idle-timeout:</strong></p><p>一个连接idle状态的最长时间，超时则被释放</p><p>其他参数详见：<a href="https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing" target="_blank" rel="noreferrer">https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing</a></p><h1 id="优化-count-计数" tabindex="-1">优化 count 计数 <a class="header-anchor" href="#优化-count-计数" aria-label="Permalink to &quot;优化 count 计数&quot;">​</a></h1><p>最近我在公司优化过几个慢查询接口的性能，总结了一些心得体会拿出来跟大家一起分享一下，希望对你会有所帮助。</p><p>我们使用的数据库是<code>Mysql8</code>，使用的存储引擎是<code>Innodb</code>。这次优化除了<code>优化索引</code>之外，更多的是在优化<code>count(*)</code>。通常情况下，分页接口一般会查询两次数据库，第一次是获取具体数据，第二次是获取总的记录行数，然后把结果整合之后，再返回。查询具体数据的sql，比如是这样的：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id,</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>它没有性能问题。但另外一条使用count(*)查询总记录行数的sql，例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user;</span></span></code></pre></div><p>却存在性能差的问题。为什么会出现这种情况呢？</p><h2 id="_1-count-为什么性能差" tabindex="-1">1 count(*)为什么性能差？ <a class="header-anchor" href="#_1-count-为什么性能差" aria-label="Permalink to &quot;1 count(*)为什么性能差？&quot;">​</a></h2><p>在Mysql中，<code>count(*)</code>的作用是统计表中记录的总行数。</p><p>而<code>count(*)</code>的性能跟存储引擎有直接关系，并非所有的存储引擎，<code>count(*)</code>的性能都很差。</p><p>在Mysql中使用最多的存储引擎是：<code>innodb</code>和<code>myisam</code>。</p><p>在myisam中会把总行数保存到磁盘上，使用count(*)时，只需要返回那个数据即可，无需额外的计算，所以执行效率很高。而innodb则不同，由于它支持事务，有<code>MVCC</code>（即多版本并发控制）的存在，在同一个时间点的不同事务中，同一条查询sql，返回的记录行数可能是不确定的。</p><p>在innodb使用count(*)时，需要从存储引擎中一行行的读出数据，然后累加起来，所以执行效率很低。</p><p>如果表中数据量小还好，一旦表中数据量很大，innodb存储引擎使用count(*)统计数据时，性能就会很差。</p><h2 id="_2-如何优化count-性能" tabindex="-1">2 如何优化count(*)性能？ <a class="header-anchor" href="#_2-如何优化count-性能" aria-label="Permalink to &quot;2 如何优化count(*)性能？&quot;">​</a></h2><p>从上面得知，既然<code>count(*)</code>存在性能问题，那么我们该如何优化呢？</p><p>我们可以从以下几个方面着手。</p><h3 id="_1-增加redis缓存" tabindex="-1">1 增加redis缓存 <a class="header-anchor" href="#_1-增加redis缓存" aria-label="Permalink to &quot;1 增加redis缓存&quot;">​</a></h3><p>对于简单的count(*)，比如：统计浏览总次数或者浏览总人数，我们可以直接将接口使用redis缓存起来，没必要实时统计。当用户打开指定页面时，在缓存中每次都设置成count = count+1即可。</p><p>用户第一次访问页面时，redis中的count值设置成1。用户以后每访问一次页面，都让count加1，最后重新设置到redis中</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212022122288.png" alt="image-20221202212241180" style="zoom:67%;"><p>这样在需要展示数量的地方，从redis中查出count值返回即可。</p><p>该场景无需从数据埋点表中使用count(*)实时统计数据，性能将会得到极大的提升。</p><p>不过在高并发的情况下，可能会存在缓存和数据库的数据不一致的问题。</p><p>但对于统计浏览总次数或者浏览总人数这种业务场景，对数据的准确性要求并不高，容忍数据不一致的情况存在。</p><h3 id="_2-加二级缓存" tabindex="-1">2 加二级缓存 <a class="header-anchor" href="#_2-加二级缓存" aria-label="Permalink to &quot;2 加二级缓存&quot;">​</a></h3><p>对于有些业务场景，新增数据很少，大部分是统计数量操作，而且查询条件很多。这时候使用传统的count(*)实时统计数据，性能肯定不会好。</p><p>假如在页面中可以通过id、name、状态、时间、来源等，一个或多个条件，统计品牌数量。</p><p>这种情况下用户的组合条件比较多，增加联合索引也没用，用户可以选择其中一个或者多个查询条件，有时候联合索引也会失效，只能尽量满足用户使用频率最高的条件增加索引。</p><p>也就是有些组合条件可以走索引，有些组合条件没法走索引，这些没法走索引的场景，该如何优化呢？</p><p>答：使用<code>二级缓存</code>。</p><p>二级缓存其实就是内存缓存。</p><p>我们可以使用<code>caffine</code>或者<code>guava</code>实现二级缓存的功能。</p><p>目前<code>SpringBoot</code>已经集成了caffine，使用起来非常方便。</p><p>只需在需要增加二级缓存的查询方法中，使用<code>@Cacheable</code>注解即可。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Cacheable</span><span style="color:#89DDFF;">(</span><span style="color:#FFCB6B;">value</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">brand</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">keyGenerator</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">cacheKeyGenerator</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">BrandModel</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBrand</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Condition</span><span style="color:#A6ACCD;"> condition</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">getBrandByCondition</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">condition</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>然后自定义cacheKeyGenerator，用于指定缓存的key。</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">class</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">CacheKeyGenerator</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">implements</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">KeyGenerator</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">@</span><span style="color:#C792EA;">Override</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">public</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">generate</span><span style="color:#89DDFF;">(</span><span style="color:#C792EA;">Object</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">target</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Method</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">method</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">Object</span><span style="color:#89DDFF;">...</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">params</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#A6ACCD;"> target</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getClass</span><span style="color:#89DDFF;">().</span><span style="color:#82AAFF;">getSimpleName</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">UNDERLINE</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> method</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getName</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">,</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#A6ACCD;">                </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> StringUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">arrayToDelimitedString</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">params</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">,</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><p>这个key是由各个条件组合而成。这样通过某个条件组合查询出品牌的数据之后，会把结果缓存到内存中，设置过期时间为5分钟。后面用户在5分钟内，使用相同的条件，重新查询数据时，可以直接从二级缓存中查出数据，直接返回了。</p><p>这样能够极大的提示count(*)的查询效率。但是如果使用二级缓存，可能存在不同的服务器上，数据不一样的情况。我们需要根据实际业务场景来选择，没法适用于所有业务场景。</p><h3 id="_3-多线程执行" tabindex="-1">3 多线程执行 <a class="header-anchor" href="#_3-多线程执行" aria-label="Permalink to &quot;3 多线程执行&quot;">​</a></h3><p>不知道你有没有做过这样的需求：统计有效订单有多少，无效订单有多少。</p><p>这种情况一般需要写两条sql，统计有效订单的sql如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>统计无效订单的sql如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>但如果在一个接口中，同步执行这两条sql效率会非常低。</p><p>这时候，可以改成成一条sql：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">),</span><span style="color:#F78C6C;">status</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> order </span><span style="color:#F78C6C;">group by</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>使用<code>group by</code>关键字分组统计相同status的数量，只会产生两条记录，一条记录是有效订单数量，另外一条记录是无效订单数量。但有个问题：status字段只有1和0两个值，重复度很高，区分度非常低，不能走索引，会全表扫描，效率也不高。还有其他的解决方案不？答：使用多线程处理。</p><p>我们可以使用<code>CompleteFuture</code>使用两个<code>线程</code>异步调用统计有效订单的sql和统计无效订单的sql，最后汇总数据，这样能够提升查询接口的性能。</p><h3 id="_4-减少join的表" tabindex="-1">4 减少join的表 <a class="header-anchor" href="#_4-减少join的表" aria-label="Permalink to &quot;4 减少join的表&quot;">​</a></h3><p>大部分的情况下，使用count(*)是为了实时统计总数量的。</p><p>但如果表本身的数据量不多，但join的表太多，也可能会影响count(*)的效率。</p><p>比如在查询商品信息时，需要根据商品名称、单位、品牌、分类等信息查询数据。</p><p>这时候写一条sql可以查出想要的数据，比如下面这样的：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> product p</span></span>
<span class="line"><span style="color:#F78C6C;">inner join</span><span style="color:#A6ACCD;"> unit u </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> p.unit_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> u.id</span></span>
<span class="line"><span style="color:#F78C6C;">inner join</span><span style="color:#A6ACCD;"> brand b </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> p.brand_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> b.id</span></span>
<span class="line"><span style="color:#F78C6C;">inner join</span><span style="color:#A6ACCD;"> category c </span><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> p.category_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> c.id</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> p.name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">测试商品</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> u.id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> b.id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">124</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> c.id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">125</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>使用product表去<code>join</code>了unit、brand和category这三张表。其实这些查询条件，在product表中都能查询出数据，没必要join额外的表。我们可以把sql改成这样：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">count</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> product</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">测试商品</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> unit_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">123</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> brand_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">124</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> category_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">125</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>在count(*)时只查product单表即可，去掉多余的表join，让查询效率可以提升不少。</p><h3 id="_5-改成clickhouse" tabindex="-1">5 改成ClickHouse <a class="header-anchor" href="#_5-改成clickhouse" aria-label="Permalink to &quot;5 改成ClickHouse&quot;">​</a></h3><p>有些时候，join的表实在太多，没法去掉多余的join，该怎么办呢？</p><p>比如上面的例子中，查询商品信息时，需要根据商品名称、单位名称、品牌名称、分类名称等信息查询数据。</p><p>这时候根据product单表是没法查询出数据的，必须要去<code>join</code>：unit、brand和category这三张表，这时候该如何优化呢？答：可以将数据保存到<code>ClickHouse</code>。</p><p>ClickHouse是基于<code>列存储</code>的数据库，不支持事务，查询性能非常高，号称查询十几亿的数据，能够秒级返回。</p><p>为了避免对业务代码的嵌入性，可以使用<code>Canal</code>监听<code>Mysql</code>的<code>binlog</code>日志。当product表有数据新增时，需要同时查询出单位、品牌和分类的数据，生成一个新的结果集，保存到ClickHouse当中。</p><p>查询数据时，从ClickHouse当中查询，这样使用count(*)的查询效率能够提升N倍。</p><blockquote><p>需要特别提醒一下：使用ClickHouse时，新增数据不要太频繁，尽量批量插入数据。</p></blockquote><p>其实如果查询条件非常多，使用ClickHouse也不是特别合适，这时候可以改成<code>ElasticSearch</code>，不过它跟Mysql一样，存在<code>深分页</code>问题。</p><h2 id="_3-count的各种用法性能对比" tabindex="-1">3 count的各种用法性能对比 <a class="header-anchor" href="#_3-count的各种用法性能对比" aria-label="Permalink to &quot;3 count的各种用法性能对比&quot;">​</a></h2><p>既然说到count(*)，就不能不说一下count家族的其他成员，比如：count(1)、count(id)、count(普通索引列)、count(未加索引列)。那么它们有什么区别呢？</p><blockquote><ul><li>count(*) ：它会获取所有行的数据，不做任何处理，行数加1。</li><li>count(1)：它会获取所有行的数据，每行固定值1，也是行数加1。</li><li>count(id)：id代表主键，它需要从所有行的数据中解析出id字段，其中id肯定都不为NULL，行数加1。</li><li>count(普通索引列)：它需要从所有行的数据中解析出普通索引列，然后判断是否为NULL，如果不是NULL，则行数+1。</li><li>count(未加索引列)：它会全表扫描获取所有数据，解析中未加索引列，然后判断是否为NULL，如果不是NULL，则行数+1。</li></ul></blockquote><p>由此，最后count的性能从高到低是：</p><blockquote><p>count(*) ≈ count(1) &gt; count(id) &gt; count(普通索引列) &gt; count(未加索引列)</p></blockquote><p>所以，其实<code>count(*)</code>是最快的。</p><p>意不意外，惊不惊喜？</p><p>千万别跟<code>select *</code> 搞混了。</p><h1 id="美团一面-如何优化慢sql" tabindex="-1">美团一面：如何优化慢SQL？ <a class="header-anchor" href="#美团一面-如何优化慢sql" aria-label="Permalink to &quot;美团一面：如何优化慢SQL？&quot;">​</a></h1><p>在应用开发的早期，数据量少，开发人员开发功能时更重视功能上的实现，随着生产数据的增长，很多 SQL 语句开始暴露出性能问题，对生产的影响也越来越大，有时可能这些有问题的 SQL 就是整个系统性能的瓶颈。</p><h2 id="sql-优化一般步骤" tabindex="-1">SQL 优化一般步骤 <a class="header-anchor" href="#sql-优化一般步骤" aria-label="Permalink to &quot;SQL 优化一般步骤&quot;">​</a></h2><h3 id="_1-慢查询日志定位执行效率低的-sql-语句" tabindex="-1">1 慢查询日志定位执行效率低的 SQL 语句 <a class="header-anchor" href="#_1-慢查询日志定位执行效率低的-sql-语句" aria-label="Permalink to &quot;1 慢查询日志定位执行效率低的 SQL 语句&quot;">​</a></h3><h3 id="_2-explain-分析sql的执行计划" tabindex="-1">2 explain 分析SQL的执行计划 <a class="header-anchor" href="#_2-explain-分析sql的执行计划" aria-label="Permalink to &quot;2 explain 分析SQL的执行计划&quot;">​</a></h3><p>需要重点关注 type、rows、filtered、extra。type 由上至下，效率越来越高：</p><blockquote><ul><li>ALL 全表扫描</li><li>index 索引全扫描</li><li>range 索引范围扫描，常用语&lt;,&lt;=,&gt;=,between,in 等操作</li><li>ref 使用非唯一索引扫描或唯一索引前缀扫描，返回单条记录，常出现在关联查询中</li><li>eq_ref 类似 ref，区别在于使用的是唯一索引，使用主键的关联查询</li><li>const/system 单条记录，系统会把匹配行中的其他列作为常数处理，如主键或唯一索引查询</li><li>null MySQL 不访问任何表或索引，直接返回结果</li><li>虽然上至下，效率越来越高，但是根据 cost 模型，假设有两个索引 idx1(a, b, c),idx2(a, c)，SQL 为&quot;select * from t where a = 1 and b in (1, 2) order by c&quot;;如果走 idx1，那么是 type 为 range，如果走 idx2，那么 type 是 ref；当需要扫描的行数，使用 idx2 大约是 idx1 的 5 倍以上时，会用 idx1，否则会用 idx2</li></ul></blockquote><p>Extra：</p><blockquote><ul><li>**Using filesort：**MySQL 需要额外的一次传递，以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配 WHERE 子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序，并按排序顺序检索行；</li><li>**Using temporary：**使用了临时表保存中间结果，性能特别差，需要重点优化；</li><li>**Using index：**表示相应的 select 操作中使用了覆盖索引（Coveing Index）,避免访问了表的数据行，效率不错！如果同时出现 using where，意味着无法直接通过索引查找来查询到符合条件的数据；</li><li>**Using index condition：**MySQL5.6 之后新增的 ICP，using index condtion 就是使用了 ICP（索引下推），在存储引擎层进行数据过滤，而不是在服务层过滤，利用索引现有的数据减少回表的数据。</li></ul></blockquote><h3 id="_3-show-profile-分析" tabindex="-1">3 show profile 分析 <a class="header-anchor" href="#_3-show-profile-分析" aria-label="Permalink to &quot;3 show profile 分析&quot;">​</a></h3><p>了解 SQL 执行的线程的状态及消耗的时间。默认是关闭的，开启语句“set profiling = 1;”</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">SHOW PROFILES ;</span></span>
<span class="line"><span style="color:#A6ACCD;">SHOW </span><span style="color:#F78C6C;">PROFILE</span><span style="color:#A6ACCD;"> FOR QUERY  #{id};</span></span></code></pre></div><h3 id="_4-trace" tabindex="-1">4 trace <a class="header-anchor" href="#_4-trace" aria-label="Permalink to &quot;4 trace&quot;">​</a></h3><p>trace 分析优化器如何选择执行计划，通过 trace 文件能够进一步了解为什么优惠券选择 A 执行计划而不选择 B 执行计划。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> optimizer_trace</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">enabled=on</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> optimizer_trace_max_mem_size</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1000000</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> information_schema.optimizer_trace;</span></span></code></pre></div><h2 id="确定问题并采用相应的措施" tabindex="-1">确定问题并采用相应的措施 <a class="header-anchor" href="#确定问题并采用相应的措施" aria-label="Permalink to &quot;确定问题并采用相应的措施&quot;">​</a></h2><blockquote><ul><li>优化索引</li><li>优化 SQL 语句：修改 SQL、IN 查询分段、时间查询分段、基于上一次数据过滤</li><li>改用其他实现方式：ES、数仓等</li><li>数据碎片处理</li></ul></blockquote><h2 id="场景分析" tabindex="-1">场景分析 <a class="header-anchor" href="#场景分析" aria-label="Permalink to &quot;场景分析&quot;">​</a></h2><h3 id="_1-最左匹配" tabindex="-1">1 最左匹配 <a class="header-anchor" href="#_1-最左匹配" aria-label="Permalink to &quot;1 最左匹配&quot;">​</a></h3><p>索引：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_shopid_orderno</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">shop_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order_no</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>SQL 语句：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> orderno</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;&#39;</span></span></code></pre></div><blockquote><p>查询匹配从左往右匹配，要使用 order_no 走索引，必须查询条件携带 shop_id 或者索引（shop_id，order_no）调换前后顺序。</p></blockquote><h3 id="_2-隐式转换" tabindex="-1">2 隐式转换 <a class="header-anchor" href="#_2-隐式转换" aria-label="Permalink to &quot;2 隐式转换&quot;">​</a></h3><p>索引：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_mobile</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">mobile</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>SQL 语句：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> mobile</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">12345678901</span></span></code></pre></div><p>隐式转换相当于在索引上做运算，会让索引失效。mobile 是字符类型，使用了数字，应该使用字符串匹配，否则 MySQL 会用到隐式替换，导致索引失效。</p><h3 id="_3-大分页" tabindex="-1">3 大分页 <a class="header-anchor" href="#_3-大分页" aria-label="Permalink to &quot;3 大分页&quot;">​</a></h3><p>索引</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_a_b_c</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">a</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">b</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">c</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>SQL 语句：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> c </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10000</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>对于大分页的场景，可以优先让产品优化需求，如果没有优化的，有如下两种优化方式：</p><blockquote><ul><li>一种是把上一次的最后一条数据，也即上面的 c 传过来，然后做“c &lt; xxx”处理，但是这种一般需要改接口协议，并不一定可行</li><li>另一种是采用延迟关联的方式进行处理，减少 SQL 回表，但是要记得索引需要完全覆盖才有效果。</li></ul></blockquote><p>SQL 改动如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> t1.</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t t1, (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> b </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">                         </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> c </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10000</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">) t2 </span></span>
<span class="line"><span style="color:#A6ACCD;">                         </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> t1.id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> t2.id;</span></span></code></pre></div><h3 id="_4-in-order-by" tabindex="-1">4 in + order by <a class="header-anchor" href="#_4-in-order-by" aria-label="Permalink to &quot;4 in + order by&quot;">​</a></h3><p>索引：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_shopid_status_created</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">shop_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order_status</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">created_at</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>SQL 语句：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> shop_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> order_status </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> created_at </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span></span></code></pre></div><p>in 查询在 MySQL 底层是通过 n*m 的方式去搜索，类似 union，但是效率比 union 高。</p><p>in 查询在进行 cost 代价计算时（代价 = 元组数 * IO 平均值），是通过将 in 包含的数值，一条条去查询获取元组数的，因此这个计算过程会比较的慢。</p><p>所以 MySQL 设置了个临界值（eq_range_index_dive_limit），5.6 之后超过这个临界值后该列的 cost 就不参与计算了。因此会导致执行计划选择不准确。</p><p>默认是 200，即 in 条件超过了 200 个数据，会导致 in 的代价计算存在问题，可能会导致 MySQL 选择的索引不准确。</p><p>**处理方式：**可以（order_status，created_at）互换前后顺序，并且调整 SQL 为延迟关联。</p><h3 id="_5-范围查询阻断-后续字段不能走索引" tabindex="-1">5 范围查询阻断，后续字段不能走索引 <a class="header-anchor" href="#_5-范围查询阻断-后续字段不能走索引" aria-label="Permalink to &quot;5 范围查询阻断，后续字段不能走索引&quot;">​</a></h3><p>索引：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_shopid_created_status</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">shop_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">created_at</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order_status</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>SQL 语句：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> shop_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> created_at </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2021-01-01 00:00:00</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> order_status </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span></span></code></pre></div><p>范围查询还有“IN、between”。</p><h3 id="_6-不等于不包含不能用到索引的快速搜索" tabindex="-1">6 不等于不包含不能用到索引的快速搜索 <a class="header-anchor" href="#_6-不等于不包含不能用到索引的快速搜索" aria-label="Permalink to &quot;6 不等于不包含不能用到索引的快速搜索&quot;">​</a></h3><p>可以用到 ICP：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> shop_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> order_status </span><span style="color:#F78C6C;">not</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> shop_id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> order_status </span><span style="color:#89DDFF;">!=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span></code></pre></div><blockquote><p>在索引上，避免使用 NOT、!=、&lt;&gt;、!&lt;、!&gt;、NOT EXISTS、NOT IN、NOT LIKE等。</p></blockquote><h3 id="_7-优化器选择不使用索引的情况" tabindex="-1">7 优化器选择不使用索引的情况 <a class="header-anchor" href="#_7-优化器选择不使用索引的情况" aria-label="Permalink to &quot;7 优化器选择不使用索引的情况&quot;">​</a></h3><blockquote><p>如果要求访问的数据量很小，则优化器还是会选择辅助索引，但是当访问的数据占整个表中数据的蛮大一部分时（一般是 20% 左右），优化器会选择通过聚集索引来查找数据。</p></blockquote><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _order </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;">  order_status </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span></code></pre></div><blockquote><p>查询出所有未支付的订单，一般这种订单是很少的，即使建了索引，也没法使用索引。</p></blockquote><h3 id="_8-复杂查询" tabindex="-1">8 复杂查询 <a class="header-anchor" href="#_8-复杂查询" aria-label="Permalink to &quot;8 复杂查询&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(amt) </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> b </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> c </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2020-01-01</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> a </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> b </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> c </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2020-01-01</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><blockquote><p>如果是统计某些数据，可能改用数仓进行解决；如果是业务上就有那么复杂的查询，可能就不建议继续走 SQL 了，而是采用其他的方式进行解决，比如使用 ES 等进行解决。</p></blockquote><h3 id="_9-asc-和-desc-混用" tabindex="-1">9 asc 和 desc 混用 <a class="header-anchor" href="#_9-asc-和-desc-混用" aria-label="Permalink to &quot;9 asc 和 desc 混用&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> _t </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> a</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> b </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;">, c </span><span style="color:#F78C6C;">asc</span></span></code></pre></div><blockquote><p>desc 和 asc 混用时会导致索引失效。</p></blockquote><h3 id="_10-大数据" tabindex="-1">10 大数据 <a class="header-anchor" href="#_10-大数据" aria-label="Permalink to &quot;10 大数据&quot;">​</a></h3><blockquote><p>对于推送业务的数据存储，可能数据量会很大，如果在方案的选择上，最终选择存储在 MySQL 上，并且做 7 天等有效期的保存。那么需要注意，频繁清理数据，会造成数据碎片，需要联系 DBA 进行数据碎片处理。</p></blockquote><h1 id="sql优化思路-经典案例分析" tabindex="-1">SQL优化思路+经典案例分析 <a class="header-anchor" href="#sql优化思路-经典案例分析" aria-label="Permalink to &quot;SQL优化思路+经典案例分析&quot;">​</a></h1><p>SQL调优这块呢，大厂面试必问的。最近金九银十嘛，所以整理了SQL的调优思路，并且附几个经典案例分析。</p><h2 id="_1-慢sql优化思路" tabindex="-1">1 慢SQL优化思路 <a class="header-anchor" href="#_1-慢sql优化思路" aria-label="Permalink to &quot;1 慢SQL优化思路&quot;">​</a></h2><blockquote><ol><li>慢查询日志记录慢SQL</li><li>explain分析SQL的执行计划</li><li>profile 分析执行耗时</li><li>Optimizer Trace分析详情</li><li>确定问题并采用相应的措施</li></ol></blockquote><h3 id="_1-慢查询日志记录慢sql" tabindex="-1">1 慢查询日志记录慢SQL <a class="header-anchor" href="#_1-慢查询日志记录慢sql" aria-label="Permalink to &quot;1 慢查询日志记录慢SQL&quot;">​</a></h3><p>如何定位慢SQL呢、我们可以通过<strong>慢查询日志</strong>来查看慢SQL。默认的情况下呢，MySQL数据库是不开启慢查询日志（<code>slow query log</code>）呢。所以我们需要手动把它打开。</p><p>查看下慢查询日志配置，我们可以使用<code>show variables like &#39;slow_query_log%&#39;</code>命令，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzbeibC0VsPaGW1uJhayibq7F90j6E5VBBWcJjU5QGLWGfGoLGdhd85ToA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><ul><li><code>slow query log</code>表示慢查询开启的状态</li><li><code>slow_query_log_file</code>表示慢查询日志存放的位置</li></ul><p>我们还可以使用<code>show variables like &#39;long_query_time&#39;</code>命令，查看超过多少时间，才记录到慢查询日志，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzGj602Le7icibbXbObLJajjVT4vTU75Xs5EJjOvUfx2Sia9wXkQApykCEQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><ul><li><code>long_query_time</code>表示查询超过多少秒才记录到慢查询日志。</li></ul><blockquote><p><strong>我们可以通过慢查日志，定位那些执行效率较低的SQL语句，重点关注分析。</strong></p></blockquote><h3 id="_2-explain查看分析sql的执行计划" tabindex="-1">2 explain查看分析SQL的执行计划 <a class="header-anchor" href="#_2-explain查看分析sql的执行计划" aria-label="Permalink to &quot;2 explain查看分析SQL的执行计划&quot;">​</a></h3><p>当定位出查询效率低的SQL后，可以使用<code>explain</code>查看<code>SQL</code>的执行计划。</p><p>当<code>explain</code>与<code>SQL</code>一起使用时，MySQL将显示来自优化器的有关语句执行计划的信息。即<code>MySQL</code>解释了它将如何处理该语句，包括有关如何连接表以及以何种顺序连接表等信息。</p><p>一条简单SQL，使用了<code>explain</code>的效果如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzXPXladfA4DnzaEHMtJZfpcDt1ibYkCj9B6eM3jiaQo8AA6ibKugI8w8mw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>一般来说，我们需要重点关注<code>type、rows、filtered、extra、key</code>。</p><h4 id="_1-type" tabindex="-1">1 type <a class="header-anchor" href="#_1-type" aria-label="Permalink to &quot;1 type&quot;">​</a></h4><p>type表示<strong>连接类型</strong>，查看索引执行情况的一个重要指标。以下性能从好到坏依次：<code>system &gt; const &gt; eq_ref &gt; ref &gt; ref_or_null &gt; index_merge &gt; unique_subquery &gt; index_subquery &gt; range &gt; index &gt; ALL</code></p><ul><li>system：这种类型要求数据库表中只有一条数据，是<code>const</code>类型的一个特例，一般情况下是不会出现的。</li><li>const：通过一次索引就能找到数据，一般用于主键或唯一索引作为条件，这类扫描效率极高，，速度非常快。</li><li>eq_ref：常用于主键或唯一索引扫描，一般指使用主键的关联查询</li><li>ref : 常用于非主键和唯一索引扫描。</li><li>ref_or_null：这种连接类型类似于<code>ref</code>，区别在于<code>MySQL</code>会额外搜索包含<code>NULL</code>值的行</li><li>index_merge：使用了索引合并优化方法，查询使用了两个以上的索引。</li><li>unique_subquery：类似于<code>eq_ref</code>，条件用了<code>in</code>子查询</li><li>index_subquery：区别于<code>unique_subquery</code>，用于非唯一索引，可以返回重复值。</li><li>range：常用于范围查询，比如：between ... and 或 In 等操作</li><li>index：全索引扫描</li><li>ALL：全表扫描</li></ul><h4 id="_2-rows" tabindex="-1">2 rows <a class="header-anchor" href="#_2-rows" aria-label="Permalink to &quot;2 rows&quot;">​</a></h4><blockquote><p>该列表示MySQL估算要找到我们所需的记录，需要读取的行数。对于InnoDB表，此数字是估计值，并非一定是个准确值。</p></blockquote><h4 id="_3-filtered" tabindex="-1">3 filtered <a class="header-anchor" href="#_3-filtered" aria-label="Permalink to &quot;3 filtered&quot;">​</a></h4><blockquote><p>该列是一个百分比的值，表里符合条件的记录数的百分比。简单点说，这个字段表示存储引擎返回的数据在经过过滤后，剩下满足条件的记录数量的比例。</p></blockquote><h4 id="_4-extra" tabindex="-1">4 extra <a class="header-anchor" href="#_4-extra" aria-label="Permalink to &quot;4 extra&quot;">​</a></h4><p>该字段包含有关MySQL如何解析查询的其他信息，它一般会出现这几个值：</p><ul><li>Using filesort：表示按文件排序，一般是在指定的排序和索引排序不一致的情况才会出现。一般见于order by语句</li><li>Using index ：表示是否用了覆盖索引。</li><li>Using temporary: 表示是否使用了临时表,性能特别差，需要重点优化。一般多见于group by语句，或者union语句。</li><li>Using where : 表示使用了where条件过滤.</li><li>Using index condition：MySQL5.6之后新增的索引下推。在存储引擎层进行数据过滤，而不是在服务层过滤，利用索引现有的数据减少回表的数据。</li></ul><h4 id="_5-key" tabindex="-1">5 key <a class="header-anchor" href="#_5-key" aria-label="Permalink to &quot;5 key&quot;">​</a></h4><p>该列表示实际用到的索引。一般配合<code>possible_keys</code>列一起看。</p><h3 id="_3-profile-分析执行耗时" tabindex="-1">3 profile 分析执行耗时 <a class="header-anchor" href="#_3-profile-分析执行耗时" aria-label="Permalink to &quot;3 profile 分析执行耗时&quot;">​</a></h3><p><code>explain</code>只是看到<code>SQL</code>的预估执行计划，如果要了解<code>SQL</code><strong>真正的执行线程状态及消耗的时间</strong>，需要使用<code>profiling</code>。开启<code>profiling</code>参数后，后续执行的<code>SQL</code>语句都会记录其资源开销，包括<code>IO，上下文切换，CPU，内存</code>等等，我们可以根据这些开销进一步分析当前慢SQL的瓶颈再进一步进行优化。</p><p><code>profiling</code>默认是关闭，我们可以使用<code>show variables like &#39;%profil%&#39;</code>查看是否开启，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Iz2tcFhpAJmNRcWZ1WViavVZib1k0YRums6uibvibUcSNPHicibUiad5P0tvBQQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>可以使用<code>set profiling=ON</code>开启。开启后，可以运行几条SQL，然后使用<code>show profiles</code>查看一下。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzS3KLpib5PjYVp8Z9CRYcEDVEdtVVfwKCV5Z1fuurC6sZpcXDt8X6KGQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><code>show profiles</code>会显示最近发给服务器的多条语句，条数由变量<code>profiling_history_size</code>定义，默认是15。如果我们需要看单独某条SQL的分析，可以<code>show profile</code>查看最近一条SQL的分析，也可以使用<code>show profile for query id</code>（其中id就是show profiles中的QUERY_ID）查看具体一条的SQL语句分析。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzNOEvds6F5Bp4DwGgYYeZiaUqeicmicfshDh8iaC8T9q0FgpP16ftbICu4Q/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>除了查看profile ，还可以查看cpu和io，如上图。</p><h3 id="_4-optimizer-trace分析详情" tabindex="-1">4 Optimizer Trace分析详情 <a class="header-anchor" href="#_4-optimizer-trace分析详情" aria-label="Permalink to &quot;4 Optimizer Trace分析详情&quot;">​</a></h3><p>profile只能查看到SQL的执行耗时，但是无法看到SQL真正执行的过程信息，即不知道MySQL优化器是如何选择执行计划。这时候，我们可以使用<code>Optimizer Trace</code>，它可以跟踪执行语句的解析优化执行的全过程。</p><p>我们可以使用<code>set optimizer_trace=&quot;enabled=on&quot;</code>打开开关，接着执行要跟踪的SQL，最后执行<code>select * from information_schema.optimizer_trace</code>跟踪，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzlkCjMoynGgc4BuPIPcTZWedLqfkRp5FEEbcYduCy6dwBPUUDweJreA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>大家可以查看分析其执行树，会包括三个阶段：</p><ul><li>join_preparation：准备阶段</li><li>join_optimization：分析阶段</li><li>join_execution：执行阶段</li></ul><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzicK2JhjqkZAghEibiclu9a5aooh4qIZGphLquibt1bvheHoHuicfhicagYIQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="_5-确定问题并采用相应的措施" tabindex="-1">5 确定问题并采用相应的措施 <a class="header-anchor" href="#_5-确定问题并采用相应的措施" aria-label="Permalink to &quot;5 确定问题并采用相应的措施&quot;">​</a></h3><p>最后确认问题，就采取对应的措施。</p><ul><li>多数慢SQL都跟索引有关，比如不加索引，索引不生效、不合理等，这时候，我们可以<strong>优化索引</strong>。</li><li>我们还可以优化SQL语句，比如一些in元素过多问题（分批），深分页问题（基于上一次数据过滤等），进行时间分段查询</li><li>SQl没办法很好优化，可以改用ES的方式，或者数仓。</li><li>如果单表数据量过大导致慢查询，则可以考虑分库分表</li><li>如果数据库在刷脏页导致慢查询，考虑是否可以优化一些参数，跟DBA讨论优化方案</li><li>如果存量数据量太大，考虑是否可以让部分数据归档</li></ul><p>我之前写了一篇文章，有关于导致慢查询的12个原因，大家看一看一下哈:<a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247499624&amp;idx=1&amp;sn=561b9cb7fe831ca7cb2d9fd65691e85e&amp;chksm=cf222041f855a957ac50c0a53baaec6d26be32427259b2974450620f33a8c834419fe535e83d&amp;token=767319274&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">盘点MySQL慢查询的12个原因</a></p><h2 id="_2-慢查询经典案例分析" tabindex="-1">2 慢查询经典案例分析 <a class="header-anchor" href="#_2-慢查询经典案例分析" aria-label="Permalink to &quot;2 慢查询经典案例分析&quot;">​</a></h2><h3 id="_1-隐式转换" tabindex="-1">1 隐式转换 <a class="header-anchor" href="#_1-隐式转换" aria-label="Permalink to &quot;1 隐式转换&quot;">​</a></h3><p>我们创建一个用户user表</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">CREATE TABLE user (</span></span>
<span class="line"><span style="color:#A6ACCD;">  id int(11) NOT NULL AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  userId varchar(32) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  age  varchar(16) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  name varchar(255) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  PRIMARY KEY (id),</span></span>
<span class="line"><span style="color:#A6ACCD;">  KEY idx_userid (userId) USING BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE=InnoDB DEFAULT CHARSET=utf8;</span></span></code></pre></div><p><code>userId</code>字段为字串类型，是B+树的普通索引，如果查询条件传了一个数字过去，会导致索引失效。如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Izia5JUxQBf0sEfUqAFByTTvcnSMgqcgadYxfblklAWhSvsP5fHrcvibGQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>如果给数字加上&#39;&#39;,也就是说，传的是一个字符串呢，当然是走索引，如下图：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzqZeo6fSQyvwrHdMCzicyQucxZf3sH6fmEtEcwbIjbxZrsbXtJgWmheA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><blockquote><p>为什么第一条语句未加单引号就不走索引了呢？这是因为不加单引号时，是字符串跟数字的比较，它们类型不匹配，MySQL会做隐式的类型转换，把它们转换为浮点数再做比较。隐式的类型转换，索引会失效。</p></blockquote><h3 id="_2-最左匹配" tabindex="-1">2 最左匹配 <a class="header-anchor" href="#_2-最左匹配" aria-label="Permalink to &quot;2 最左匹配&quot;">​</a></h3><p>MySQl建立联合索引时，会遵循最左前缀匹配的原则，即最左优先。如果你建立一个<code>（a,b,c）</code>的联合索引，相当于建立了<code>(a)、(a,b)、(a,b,c)</code>三个索引。</p><p>假设有以下表结构：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">CREATE TABLE user (</span></span>
<span class="line"><span style="color:#A6ACCD;">  id int(11) NOT NULL AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  user_id varchar(32) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  age  varchar(16) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  name varchar(255) NOT NULL,</span></span>
<span class="line"><span style="color:#A6ACCD;">  PRIMARY KEY (id),</span></span>
<span class="line"><span style="color:#A6ACCD;">  KEY idx_userid_name (user_id,name) USING BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE=InnoDB DEFAULT CHARSET=utf8;</span></span></code></pre></div><p>假设有一个联合索引<code>idx_userid_name</code>，我们现在执行以下<code>SQL</code>，如果查询列是<code>name</code>，索引是无效的：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain select * from user where name =&#39;捡田螺的小男孩&#39;;</span></span></code></pre></div><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Iz08pJVC0q2r4TRlOtHzfOcTNInkUKvXyic0hQy5icHFGeDFW7neSEP6Bg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>因为查询条件列<code>name</code>不是联合索引<code>idx_userid_name</code>中的第一个列，不满足最左匹配原则，所以索引不生效。在联合索引中，只有查询条件满足最左匹配原则时，索引才正常生效。如下，查询条件列是<code>user_id</code></p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Iz9wphdo7GTf8w1iccyVuQPOVxn75TC3lXFNdaBDKtAUtV17ibLREmkMyA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="_3-深分页问题" tabindex="-1">3 深分页问题 <a class="header-anchor" href="#_3-深分页问题" aria-label="Permalink to &quot;3 深分页问题&quot;">​</a></h3><p><code>limit</code>深分页问题，会导致慢查询，应该大家都司空见惯了吧。</p><p><strong>limit深分页为什么会变慢呢？</strong> 假设有表结构如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">CREATE TABLE account (</span></span>
<span class="line"><span style="color:#A6ACCD;">  id int(11) NOT NULL AUTO_INCREMENT COMMENT &#39;主键Id&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  name varchar(255) DEFAULT NULL COMMENT &#39;账户名&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  balance int(11) DEFAULT NULL COMMENT &#39;余额&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  create_time datetime NOT NULL COMMENT &#39;创建时间&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  update_time datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT &#39;更新时间&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  PRIMARY KEY (id),</span></span>
<span class="line"><span style="color:#A6ACCD;">  KEY idx_name (name),</span></span>
<span class="line"><span style="color:#A6ACCD;">  KEY idx_create_time (create_time) //索引</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT=&#39;账户表&#39;;</span></span></code></pre></div><p>以下这个SQL，你知道执行过程是怎样的呢？</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select id,name,balance from account where create_time&gt; &#39;2020-09-19&#39; limit 100000,10;</span></span></code></pre></div><p>这个SQL的执行流程酱紫：</p><ol><li>通过普通二级索引树<code>idx_create_time</code>，过滤<code>create_time</code>条件，找到满足条件的主键<code>id</code>。</li><li>通过主键<code>id</code>，回到<code>id</code>主键索引树，找到满足记录的行，然后取出需要展示的列（回表过程）</li><li>扫描满足条件的<code>100010</code>行，然后扔掉前<code>100000</code>行，返回。</li></ol><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzLfglfdGPqJFWzSOGvhT75L7JWx8r8XkNMdtnUjMicickhicsBEiaqUPILA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>因此，limit深分页，导致SQL变慢原因有两个：</p><ul><li><code>limit</code>语句会先扫描<code>offset+n</code>行，然后再丢弃掉前<code>offset</code>行，返回后<code>n</code>行数据。也就是说<code>limit 100000,10</code>，就会扫描<code>100010</code>行，而<code>limit 0,10</code>，只扫描<code>10</code>行。</li><li><code>limit 100000,10</code> 扫描更多的行数，也意味着回表更多的次数。</li></ul><p><strong>如何优化深分页问题?</strong></p><p>我们可以通过减少回表次数来优化。一般有<strong>标签记录法和延迟关联法</strong>。</p><p><strong>标签记录法</strong></p><blockquote><p>就是标记一下上次查询到哪一条了，下次再来查的时候，从该条开始往下扫描。就好像看书一样，上次看到哪里了，你就折叠一下或者夹个书签，下次来看的时候，直接就翻到啦。</p></blockquote><p>假设上一次记录到<code>100000</code>，则SQL可以修改为：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select  id,name,balance FROM account where id &gt; 100000 limit 10;</span></span></code></pre></div><p>这样的话，后面无论翻多少页，性能都会不错的，因为命中了id索引。但是这种方式有局限性：需要一种类似连续自增的字段。</p><p><strong>延迟关联法</strong></p><p>延迟关联法，就是把条件转移到<strong>主键索引树</strong>，然后减少回表。如下</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select  acct1.id,acct1.name,acct1.balance FROM account acct1 INNER JOIN (SELECT a.id FROM account a WHERE a.create_time &gt; &#39;2020-09-19&#39; limit 100000, 10) AS acct2 on acct1.id= acct2.id;</span></span></code></pre></div><p><strong>优化思路</strong>就是，先通过<code>idx_create_time</code>二级索引树查询到满足条件的<code>主键ID</code>，再与原表通过<code>主键ID</code>内连接，这样后面直接走了主键索引了，同时也减少了回表。</p><h3 id="_4-in元素过多" tabindex="-1">4 in元素过多 <a class="header-anchor" href="#_4-in元素过多" aria-label="Permalink to &quot;4  in元素过多&quot;">​</a></h3><p>如果使用了<code>in</code>，即使后面的条件加了索引，还是要注意<code>in</code>后面的元素不要过多哈。<code>in</code>元素一般建议不要超过<code>200</code>个，如果超过了，建议分组，每次200一组进行哈。</p><p><strong>反例:</strong></p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select user_id,name from user where user_id in (1,2,3...1000000);</span></span></code></pre></div><p>如果我们对<code>in</code>的条件不做任何限制的话，该查询语句一次性可能会查询出非常多的数据，很容易导致接口超时。尤其有时候，我们是用的子查询，<strong>in后面的子查询</strong>，你都不知道数量有多少那种，更容易采坑.如下这种子查询：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select * from user where user_id in (select author_id from artilce where type = 1);</span></span></code></pre></div><p>如果<code>type = 1</code>有1一千，甚至上万个呢？肯定是慢SQL。索引一般建议分批进行，一次200个，比如：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select user_id,name from user where user_id in (1,2,3...200);</span></span></code></pre></div><p>in查询为什么慢呢？</p><blockquote><p>这是因为<code>in</code>查询在MySQL底层是通过<code>n*m</code>的方式去搜索，类似<code>union</code>。</p><p>in查询在进行cost代价计算时（代价 = 元组数 * IO平均值），是通过将in包含的数值，一条条去查询获取元组数的，因此这个计算过程会比较的慢，所以MySQL设置了个临界值(eq_range_index_dive_limit)，5.6之后超过这个临界值后该列的cost就不参与计算了。因此会导致执行计划选择不准确。默认是200，即in条件超过了200个数据，会导致in的代价计算存在问题，可能会导致Mysql选择的索引不准确。</p></blockquote><h3 id="_5-order-by-走文件排序" tabindex="-1">5 order by 走文件排序 <a class="header-anchor" href="#_5-order-by-走文件排序" aria-label="Permalink to &quot;5 order by 走文件排序&quot;">​</a></h3><p>如果order by 使用到文件排序，则会可能会产生慢查询。我们来看下下面这个SQL：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select name,age,city from staff where city = &#39;深圳&#39; order by age limit 10;</span></span></code></pre></div><p>它表示的意思就是：查询前10个，来自深圳员工的姓名、年龄、城市，并且按照年龄小到大排序。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzccZfvNm8IGiaRm5MbdCWxCQmF5khK8wCicic5NeJsDpCpibGOtG0JfsR0Q/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>查看explain执行计划的时候，可以看到Extra这一列，有一个<code>Using filesort</code>，它表示用到文件排序。</p><p><strong>order by文件排序效率为什么较低</strong></p><p>大家可以看下这个下面这个图:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Izz29STbibSS6BepYE5MG639zORXiblwGJcw3IlZgQdibiaiav1V7TOWAkIYw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><code>order by</code>排序，分为<code>全字段排序</code>和<code>rowid排序</code>。它是拿<code>max_length_for_sort_data</code>和结果行数据长度对比，如果结果行数据长度超过<code>max_length_for_sort_data</code>这个值，就会走<code>rowid</code>排序，相反，则走全字段排序。</p><h4 id="_1-rowid排序" tabindex="-1">1 rowid排序 <a class="header-anchor" href="#_1-rowid排序" aria-label="Permalink to &quot;1 rowid排序&quot;">​</a></h4><p>rowid排序，一般需要回表去找满足条件的数据，所以效率会慢一点。以下这个SQL，使用rowid排序，执行过程是这样：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,age,city </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> staff </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> city </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">深圳</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> age </span><span style="color:#F78C6C;">limit</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><ol><li><code>MySQL</code>为对应的线程初始化<code>sort_buffer</code>，放入需要排序的<code>age</code>字段，以及<code>主键id</code>；</li><li>从索引树<code>idx_city</code>， 找到第一个满足 <code>city=&#39;深圳’</code>条件的<code>主键id</code>,假设<code>id</code>为<code>X</code>；</li><li>到主键<code>id索引树</code>拿到<code>id=X</code>的这一行数据， 取age和主键id的值，存到<code>sort_buffer</code>；</li><li>从索引树<code>idx_city</code>拿到下一个记录的<code>主键id</code>，假设<code>id=Y</code>；</li><li>重复步骤 3、4 直到<code>city</code>的值不等于深圳为止；</li><li>前面5步已经查找到了所有<code>city</code>为深圳的数据，在<code>sort_buffer</code>中，将所有数据根据<code>age</code>进行排序；遍历排序结果，取前10行，并按照id的值回到原表中，取出<code>city、name 和 age</code>三个字段返回给客户端。</li></ol><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Iz1xNWsicI3t7pGnpTGvsd6HTFMc8W66Yu9ZyXw0Z9RkMtXib8qGYzsoFQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h4 id="_2-全字段排序" tabindex="-1">2 全字段排序 <a class="header-anchor" href="#_2-全字段排序" aria-label="Permalink to &quot;2 全字段排序&quot;">​</a></h4><p>同样的SQL，如果是走全字段排序是这样的：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select name,age,city from staff where city = &#39;深圳&#39; order by age limit 10;</span></span></code></pre></div><ol><li>MySQL 为对应的线程初始化<code>sort_buffer</code>，放入需要查询的<code>name、age、city</code>字段；</li><li>从索引树<code>idx_city</code>， 找到第一个满足 <code>city=&#39;深圳’</code>条件的主键 id，假设找到<code>id=X</code>；</li><li>到主键id索引树拿到<code>id=X</code>的这一行数据， 取<code>name、age、city</code>三个字段的值，存到<code>sort_buffer</code>；</li><li>从索引树<code>idx_city</code> 拿到下一个记录的主键<code>id</code>，假设<code>id=Y</code>；</li><li>重复步骤 3、4 直到<code>city</code>的值不等于深圳为止；</li><li>前面5步已经查找到了所有<code>city</code>为深圳的数据，在<code>sort_buffer</code>中，将所有数据根据age进行排序；</li><li>按照排序结果取前10行返回给客户端。</li></ol><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzDOtHulKq6Jxa1QVDnITlGibj9icouQK6TVPPllL2RlwxDEIh0HJVLmkA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><code>sort_buffer</code>的大小是由一个参数控制的：<code>sort_buffer_size</code>。</p><ul><li>如果要排序的数据小于<code>sort_buffer_size</code>，排序在<code>sort_buffer</code>内存中完成</li><li>如果要排序的数据大于<code>sort_buffer_size</code>，则借助磁盘文件来进行排序。</li></ul><blockquote><p>借助磁盘文件排序的话，效率就更慢一点。因为先把数据放入<code>sort_buffer</code>，当快要满时。会排一下序，然后把<code>sort_buffer</code>中的数据，放到临时磁盘文件，等到所有满足条件数据都查完排完，再用归并算法把磁盘的临时排好序的小文件，合并成一个有序的大文件。</p></blockquote><h4 id="_3-如何优化order-by的文件排序" tabindex="-1">3 如何优化order by的文件排序 <a class="header-anchor" href="#_3-如何优化order-by的文件排序" aria-label="Permalink to &quot;3  如何优化order by的文件排序&quot;">​</a></h4><p><code>order by</code>使用文件排序，效率会低一点。我们怎么优化呢？</p><ul><li>因为数据是无序的，所以就需要排序。如果数据本身是有序的，那就不会再用到文件排序啦。而索引数据本身是有序的，我们通过建立索引来优化<code>order by</code>语句。</li><li>我们还可以通过调整<code>max_length_for_sort_data、sort_buffer_size</code>等参数优化；</li></ul><h3 id="_6-索引字段上使用is-null-is-not-null-索引可能失效" tabindex="-1">6 索引字段上使用is null， is not null，索引可能失效 <a class="header-anchor" href="#_6-索引字段上使用is-null-is-not-null-索引可能失效" aria-label="Permalink to &quot;6 索引字段上使用is null， is not null，索引可能失效&quot;">​</a></h3><p>表结构:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">card</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_card</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">card</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><p>单个<code>name</code>字段加上索引，并查询<code>name</code>为非空的语句，其实会走索引的，如下:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzY2yMYdY3W6hXDUT2suuGqic3VuS07oDGL1T0PgMxicfX7MPnr7g6sYqg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>单个<code>card</code>字段加上索引，并查询<code>name</code>为非空的语句，其实会走索引的，如下:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzH77Ie3ic0Ax7ia0l1heO5HvAUswiab8hBM6Xp3uZvlVrQd8VK5fFyrd8Q/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>但是它两用or连接起来，索引就失效了，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzSIcwbMLAanYvLtRlYS1Nx0K38GTB5x0EFeV20MiclECporXa7rn3now/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>很多时候，也是因为数据量问题，导致了MySQL优化器放弃走索引。同时，平时我们用<code>explain</code>分析SQL的时候，如果<code>type=range</code>,要注意一下哈，因为这个可能因为数据量问题，导致索引无效。</p><h3 id="_7-索引字段上使用-或者-索引可能失效" tabindex="-1">7 索引字段上使用（！= 或者 &lt; &gt;），索引可能失效 <a class="header-anchor" href="#_7-索引字段上使用-或者-索引可能失效" aria-label="Permalink to &quot;7 索引字段上使用（！= 或者 &lt; &gt;），索引可能失效&quot;">​</a></h3><p>假设有表结构：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><p>虽然age加了索引，但是使用了！= 或者&lt; &gt;，not in这些时，索引如同虚设。如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IztlWINFdAfteJwovqXjY83dW6u76s14LicdbvAGC4Z855PSnNHwibicPLg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>其实这个也是跟mySQL优化器有关，如果优化器觉得即使走了索引，还是需要扫描很多很多行的哈，它觉得不划算，不如直接不走索引。平时我们用！= 或者&lt; &gt;，not in的时候，留点心眼哈。</p><h3 id="_8-左右连接-关联的字段编码格式不一样" tabindex="-1">8 左右连接，关联的字段编码格式不一样 <a class="header-anchor" href="#_8-左右连接-关联的字段编码格式不一样" aria-label="Permalink to &quot;8 左右连接，关联的字段编码格式不一样&quot;">​</a></h3><p>新建两个表，一个<code>user</code>，一个<code>user_job</code></p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">CHARACTER</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">SET</span><span style="color:#A6ACCD;"> utf8mb4 </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user_job</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">userId</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">job</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8;</span></span></code></pre></div><p><code>user</code>表的<code>name</code>字段编码是<code>utf8mb4</code>，而<code>user_job</code>表的<code>name</code>字段编码为<code>utf8</code>。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IztVR6zjFQ5iaATBXlpVMic2QZu8ABb9ul1oibEX8QjzdxictEWbh3MkofJg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>执行左外连接查询,<code>user_job</code>表还是走全表扫描，如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IziaHyKqgIMsqGwBXty8Xnh0xGsgNl7p9rggpetl3QiapqZpISagUNZiajA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>如果把它们的name字段改为编码一致，相同的SQL，还是会走索引。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzRukxvib3cVic4dK50whiaUiam0yWAlVm4m2EibLz0FqpAjugONyngKNSEiaQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="_9-group-by使用临时表" tabindex="-1">9 group by使用临时表 <a class="header-anchor" href="#_9-group-by使用临时表" aria-label="Permalink to &quot;9 group by使用临时表&quot;">​</a></h3><p>group by一般用于分组统计，它表达的逻辑就是根据一定的规则，进行分组。日常开发中，我们使用得比较频繁。如果不注意，很容易产生慢SQL。</p><h4 id="_2-9-1-group-by执行流程" tabindex="-1">2.9.1 group by执行流程 <a class="header-anchor" href="#_2-9-1-group-by执行流程" aria-label="Permalink to &quot;2.9.1 group by执行流程&quot;">​</a></h4><p>假设有表结构：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">CREATE TABLE `staff` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT &#39;主键id&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  `id_card` varchar(20) NOT NULL COMMENT &#39;身份证号码&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  `name` varchar(64) NOT NULL COMMENT &#39;姓名&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  `age` int(4) NOT NULL COMMENT &#39;年龄&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  `city` varchar(64) NOT NULL COMMENT &#39;城市&#39;,</span></span>
<span class="line"><span style="color:#A6ACCD;">  PRIMARY KEY (`id`)</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT=&#39;员工表&#39;;</span></span></code></pre></div><p>我们查看一下这个SQL的执行计划：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain select city ,count(*) as num from staff group by city;</span></span></code></pre></div><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzHLkBohRuntiaCQmCoKyqTxKlTPAOLibbVMEibZEhSacvgAPzZ4kSWvByA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><ul><li>Extra 这个字段的<code>Using temporary</code>表示在执行分组的时候使用了临时表</li><li>Extra 这个字段的<code>Using filesort</code>表示使用了文件排序</li></ul><p>group by是怎么使用到临时表和排序了呢？我们来看下这个SQL的执行流程</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select city ,count(*) as num from staff group by city;</span></span></code></pre></div><ol><li>创建内存临时表，表里有两个字段<code>city和num</code>；</li><li>全表扫描staff的记录，依次取出city = &#39;X&#39;的记录。</li></ol><ul><li>判断临时表中是否有为<code>city=&#39;X&#39;</code>的行，没有就插入一个记录<code> (X,1)</code>;</li><li>如果临时表中有<code>city=&#39;X&#39;</code>的行，就将X这一行的num值加 1；</li></ul><ol><li>遍历完成后，再根据字段<code>city</code>做排序，得到结果集返回给客户端。这个流程的执行图如下：</li></ol><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1Izdc4RLpYptslOIGLVJvMnYdKDwFJrT105QRQ6fM74KOsHIpUA2lRGKg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><strong>临时表的排序是怎样的呢？</strong></p><p>就是把需要排序的字段，放到sort buffer，排完就返回。在这里注意一点哈，排序分全字段排序和rowid排序</p><ul><li>如果是全字段排序，需要查询返回的字段，都放入sort buffer，根据排序字段排完，直接返回</li><li>如果是rowid排序，只是需要排序的字段放入sort buffer，然后多一次回表操作，再返回。</li></ul><h4 id="_2-9-2-group-by可能会慢在哪里" tabindex="-1">2.9.2 group by可能会慢在哪里？ <a class="header-anchor" href="#_2-9-2-group-by可能会慢在哪里" aria-label="Permalink to &quot;2.9.2 group by可能会慢在哪里？&quot;">​</a></h4><p><code>group by</code>使用不当，很容易就会产生慢<code>SQL</code>问题。因为它既用到临时表，又默认用到排序。有时候还可能用到磁盘临时表。</p><ul><li>如果执行过程中，会发现内存临时表大小到达了上限（控制这个上限的参数就是<code>tmp_table_size</code>），会把内存临时表转成磁盘临时表。</li><li>如果数据量很大，很可能这个查询需要的磁盘临时表，就会占用大量的磁盘空间。</li></ul><h4 id="_2-9-3-如何优化group-by呢" tabindex="-1">2.9.3 如何优化group by呢 <a class="header-anchor" href="#_2-9-3-如何优化group-by呢" aria-label="Permalink to &quot;2.9.3 如何优化group by呢&quot;">​</a></h4><p><strong>从哪些方向去优化呢？</strong></p><ul><li>方向1：既然它默认会排序，我们不给它排是不是就行啦。</li><li>方向2：既然临时表是影响group by性能的X因素，我们是不是可以不用临时表？</li></ul><p>我们一起来想下，执行<code>group by</code>语句为什么需要临时表呢？<code>group by</code>的语义逻辑，就是统计不同的值出现的个数。如果这个这些值一开始就是有序的，我们是不是直接往下扫描统计就好了，就不用临时表来记录并统计结果啦?</p><p>可以有这些优化方案：</p><ul><li>group by 后面的字段加索引</li><li>order by null 不用排序</li><li>尽量只使用内存临时表</li><li>使用SQL_BIG_RESULT</li></ul><h3 id="_10-delete-in子查询不走索引" tabindex="-1">10 delete + in子查询不走索引！ <a class="header-anchor" href="#_10-delete-in子查询不走索引" aria-label="Permalink to &quot;10  delete + in子查询不走索引！&quot;">​</a></h3><p>之前见到过一个生产慢SQL问题，当delete遇到in子查询时，即使有索引，也是不走索引的。而对应的select + in子查询，却可以走索引。</p><p>MySQL版本是5.7，假设当前有两张表account和old_account,表结构如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">old_account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">主键Id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户名</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">余额</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">创建时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">UPDATE</span><span style="color:#A6ACCD;"> CURRENT_TIMESTAMP COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">更新时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 ROW_FORMAT</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">REDUNDANT COMMENT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">老的账户表</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">account</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">主键Id</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户名</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">余额</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">创建时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">UPDATE</span><span style="color:#A6ACCD;"> CURRENT_TIMESTAMP COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">更新时间</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">),</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">KEY</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">idx_name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">USING</span><span style="color:#A6ACCD;"> BTREE</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1570068</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 ROW_FORMAT</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">REDUNDANT COMMENT</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">账户表</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>执行的SQL如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">delete from account where name in (select name from old_account);</span></span></code></pre></div><p>查看执行计划，发现不走索引：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzUt2KOddhxYBfRhBIPb28d9syibyCSAZ0eJlNzqfRDAkaicB8iaqVFBmzw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片">但是如果把delete换成select，就会走索引。如下：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/PoF8jo1PmpxtHVxOiagMUjb3RUGAYD1IzOZpgKVpQvopHgrj26fyHzIqdf81cOibK3Rzpviau9JWoib8B8btkckX9w/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>为什么<code>select + in</code>子查询会走索引，<code>delete + in</code>子查询却不会走索引呢？</p><p>我们执行以下SQL看看：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">explain </span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> account </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">old_account</span><span style="color:#A6ACCD;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">show WARNINGS; </span><span style="color:#89DDFF;">//</span><span style="color:#A6ACCD;">可以查看优化后,最终执行的sql</span></span></code></pre></div><p>结果如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">balance</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">semi </span><span style="color:#F78C6C;">join</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">old_account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">test2</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">old_account</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>可以发现，实际执行的时候，MySQL对select in子查询做了优化，把子查询改成join的方式，所以可以走索引。但是很遗憾，对于<code>delete in</code>子查询，MySQL却没有对它做这个优化。</p><p>日常开发中，大家注意一下这个场景哈</p><h1 id="如何写出一手好-sql-很有必要" tabindex="-1">如何写出一手好 SQL ？很有必要！ <a class="header-anchor" href="#如何写出一手好-sql-很有必要" aria-label="Permalink to &quot;如何写出一手好 SQL ？很有必要！&quot;">​</a></h1><h2 id="慢sql原因" tabindex="-1">慢SQL原因 <a class="header-anchor" href="#慢sql原因" aria-label="Permalink to &quot;慢SQL原因&quot;">​</a></h2><p>最近频繁出现慢SQL告警，<strong>执行时间最长的竟然高达5分钟</strong>。导出日志后分析，主要原因竟然是<strong>没有命中索引和没有分页处理</strong> 。其实这是非常低级的错误，我不禁后背一凉，团队成员的技术水平亟待提高啊</p><h3 id="_1-最大数据量" tabindex="-1">1 最大数据量 <a class="header-anchor" href="#_1-最大数据量" aria-label="Permalink to &quot;1 最大数据量&quot;">​</a></h3><p><strong>MySQL性能</strong>，<strong>最大数据量</strong></p><p><strong>抛开数据量和并发数，谈性能都是耍流氓</strong> 。MySQL没有限制单表最大记录数，它取决于操作系统对文件大小的限制。</p><table><thead><tr><th style="text-align:left;">文件系统</th><th style="text-align:center;">单文件大小限制</th></tr></thead><tbody><tr><td style="text-align:left;">FAT32</td><td style="text-align:center;">最大4G</td></tr><tr><td style="text-align:left;">NTFS</td><td style="text-align:center;">最大64GB</td></tr><tr><td style="text-align:left;">NTFS5.0</td><td style="text-align:center;">最大2TB</td></tr><tr><td style="text-align:left;">EXT2</td><td style="text-align:center;">块大小为1024字节，文件最大容量16GB；块大小为4096字节，文件最大容量2TB</td></tr><tr><td style="text-align:left;">EXT3</td><td style="text-align:center;">块大小为4KB，文件最大容量为4TB</td></tr><tr><td style="text-align:left;">EXT4</td><td style="text-align:center;">理论可以大于16TB</td></tr></tbody></table><blockquote><p>阿里巴巴Java开发手册提出单表行数超过500万行或者单表容量超过2GB，才推荐分库分表。性能由综合因素决定，抛开业务复杂度，影响程度依次是硬件配置、MySQL配置、数据表设计、索引优化。</p><p>500万这个值仅供参考，并非铁律。</p></blockquote><blockquote><p>博主曾经操作过超过4亿行数据的单表，分页查询最新的20条记录耗时0.6秒，SQL语句大致是 <code>select field_1,field_2 from table where id &lt; #{prePageMinId} order by id desc limit 20</code>，prePageMinId是上一页数据记录的最小ID。虽然当时查询速度还凑合，随着数据不断增长，有朝一日必定不堪重负。</p></blockquote><blockquote><p>分库分表是个周期长而风险高的大活儿，应该尽可能在当前结构上优化，比如升级硬件、迁移历史数据等等，实在没辙了再分。对分库分表感兴趣的同学可以阅读分库分表的基本思想。</p></blockquote><h3 id="_2-最大并发数" tabindex="-1">2 最大并发数 <a class="header-anchor" href="#_2-最大并发数" aria-label="Permalink to &quot;2 最大并发数&quot;">​</a></h3><p>并发数是指同一时刻数据库能处理多少个请求，由max<em>connections和max</em>user<em>connections决定。maxconnections是指MySQL实例的最大连接数，上限值是16384，max</em>user*connections是指每个数据库用户的最大连接数。</p><p>MySQL会为每个连接提供缓冲区，意味着消耗更多的内存。如果连接数设置太高硬件吃不消，太低又不能充分利用硬件。一般要求两者比值超过10%，计算方法如下：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">max_used_connections </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> max_connections </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">% </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">3</span><span style="color:#89DDFF;">/</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">% ≈ </span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">%</span></span></code></pre></div><p>查看最大连接数与响应最大连接数：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%max_connections%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;show variables </span><span style="color:#F78C6C;">like</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%max_user_connections%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>在配置文件my.cnf中修改最大连接数</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#89DDFF;">[</span><span style="color:#A6ACCD;">mysqld</span><span style="color:#89DDFF;">]</span><span style="color:#A6ACCD;">max_connections </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100max</span><span style="color:#A6ACCD;">_used_connections </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">20</span></span></code></pre></div><h2 id="查询耗时0-5秒" tabindex="-1">查询耗时0.5秒 <a class="header-anchor" href="#查询耗时0-5秒" aria-label="Permalink to &quot;查询耗时0.5秒&quot;">​</a></h2><blockquote><p>建议将单次查询耗时控制在0.5秒以内，0.5秒是个经验值，源于用户体验的 <strong>3秒原则</strong> 。如果用户的操作3秒内没有响应，将会厌烦甚至退出。响应时间=客户端UI渲染耗时+网络请求耗时+应用程序处理耗时+查询数据库耗时，0.5秒就是留给数据库1/6的处理时间。</p></blockquote><h2 id="实施原则" tabindex="-1">实施原则 <a class="header-anchor" href="#实施原则" aria-label="Permalink to &quot;实施原则&quot;">​</a></h2><blockquote><p>相比NoSQL数据库，MySQL是个娇气脆弱的家伙。它就像体育课上的女同学，一点纠纷就和同学闹别扭(扩容难)，跑两步就气喘吁吁(容量小并发低)，常常身体不适要请假(SQL约束太多)。如今大家都会搞点分布式，应用程序扩容比数据库要容易得多，所以实施原则是 <strong>数据库少干活，应用程序多干活</strong> 。</p></blockquote><blockquote><ul><li>充分利用但不滥用索引，须知索引也消耗磁盘和CPU。</li><li>不推荐使用数据库函数格式化数据，交给应用程序处理。</li><li>不推荐使用外键约束，用应用程序保证数据准确性。</li><li>写多读少的场景，不推荐使用唯一索引，用应用程序保证唯一性。</li><li>适当冗余字段，尝试创建中间表，用应用程序计算中间结果，用空间换时间。</li><li>不允许执行极度耗时的事务，配合应用程序拆分成更小的事务。</li><li>预估重要数据表（比如订单表）的负载和数据增长态势，提前优化。</li></ul></blockquote><h3 id="_1-数据类型的选择" tabindex="-1">1 数据类型的选择 <a class="header-anchor" href="#_1-数据类型的选择" aria-label="Permalink to &quot;1 数据类型的选择&quot;">​</a></h3><blockquote><ul><li>如果长度能够满足，整型尽量使用tinyint、smallint、medium_int而非int。</li><li>如果字符串长度确定，采用char类型。</li><li>如果varchar能够满足，不采用text类型。</li><li>精度要求较高的使用decimal类型，也可以使用BIGINT，比如精确两位小数就乘以100后保存。</li><li>尽量采用timestamp而非datetime</li></ul></blockquote><table><thead><tr><th style="text-align:left;">类型</th><th style="text-align:left;">占据字节</th><th style="text-align:center;">描述</th></tr></thead><tbody><tr><td style="text-align:left;">datetime</td><td style="text-align:left;">8字节</td><td style="text-align:center;">&#39;1000-01-01 00:00:00.000000&#39; to &#39;9999-12-31 23:59:59.999999</td></tr><tr><td style="text-align:left;">timestamp</td><td style="text-align:left;">4字节</td><td style="text-align:center;">&#39;1970-01-01 00:00:01.000000&#39; to &#39;2038-01-19 03:14:07.999999&#39;</td></tr></tbody></table><p>相比datetime，timestamp占用更少的空间，以UTC的格式储存自动转换时区。<a href="http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&amp;mid=2247486173&amp;idx=2&amp;sn=0603756b36279b5f5f036151af85f9fd&amp;chksm=eb538febdc2406fd70748604c500eeaea584306c137cb2020ab5a72187c9359926015e00a46d&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">MySQL开发的 36 条军规</a>建议你看下。</p><h3 id="_2-避免空值" tabindex="-1">2 避免空值 <a class="header-anchor" href="#_2-避免空值" aria-label="Permalink to &quot;2 避免空值&quot;">​</a></h3><blockquote><p>MySQL中字段为NULL时依然占用空间，会使索引、索引统计更加复杂。从NULL值更新到非NULL无法做到原地更新，容易发生索引分裂影响性能。尽可能将NULL值用有意义的值代替，也能避免SQL语句里面包含 <code>is not null</code>的判断。</p></blockquote><h3 id="_3-text类型优化" tabindex="-1">3 text类型优化 <a class="header-anchor" href="#_3-text类型优化" aria-label="Permalink to &quot;3 text类型优化&quot;">​</a></h3><blockquote><p>由于text字段储存大量数据，表容量会很早涨上去，影响其他字段的查询性能。建议抽取出来放在子表里，用业务主键关联。</p></blockquote><h3 id="_4-索引分类" tabindex="-1">4 索引分类 <a class="header-anchor" href="#_4-索引分类" aria-label="Permalink to &quot;4 索引分类&quot;">​</a></h3><blockquote><ol><li>普通索引：最基本的索引。</li><li>组合索引：多个字段上建立的索引，能够加速复合查询条件的检索。</li><li>唯一索引：与普通索引类似，但索引列的值必须唯一，允许有空值。</li><li>组合唯一索引：列值的组合必须唯一。</li><li>主键索引：特殊的唯一索引，用于唯一标识数据表中的某一条记录，不允许有空值，一般用primary key约束。</li><li>全文索引：用于海量文本的查询，MySQL5.6之后的InnoDB和MyISAM均支持全文索引。由于查询精度以及扩展性不佳，更多的企业选择Elasticsearch。</li></ol></blockquote><h3 id="_5-索引优化" tabindex="-1">5 索引优化 <a class="header-anchor" href="#_5-索引优化" aria-label="Permalink to &quot;5 索引优化&quot;">​</a></h3><blockquote><ol><li>分页查询很重要，如果查询数据量超过30%，MYSQL不会使用索引。</li><li>单表索引数不超过5个、单个索引字段数不超过5个。</li><li>字符串可使用前缀索引，前缀长度控制在5-8个字符。</li><li>字段唯一性太低，增加索引没有意义，如：是否删除、性别。</li><li>合理使用覆盖索引，如下所示：select login<em>name, nick</em>name from member where login_name = ?</li></ol></blockquote><p>login<em>name, nick</em>name两个字段建立组合索引，比login_name简单索引要更快</p><h2 id="sql优化" tabindex="-1">SQL优化 <a class="header-anchor" href="#sql优化" aria-label="Permalink to &quot;SQL优化&quot;">​</a></h2><h3 id="分批处理" tabindex="-1">分批处理 <a class="header-anchor" href="#分批处理" aria-label="Permalink to &quot;分批处理&quot;">​</a></h3><p>博主小时候看到鱼塘挖开小口子放水，水面有各种漂浮物。浮萍和树叶总能顺利通过出水口，而树枝会挡住其他物体通过，有时还会卡住，需要人工清理。MySQL就是鱼塘，最大并发数和网络带宽就是出水口，用户SQL就是漂浮物。</p><p>不带分页参数的查询或者影响大量数据的update和delete操作，都是树枝，我们要把它打散分批处理，举例说明：业务描述：更新用户所有已过期的优惠券为不可用状态。</p><p>SQL语句：<code>update status=0 FROM</code>coupon <code>WHERE expire_date &lt;= #{currentDate} and status=1;</code>如果大量优惠券需要更新为不可用状态，执行这条SQL可能会堵死其他SQL，分批处理伪代码如下：</p><div class="language-java"><button title="Copy Code" class="copy"></button><span class="lang">java</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> pageNo </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;"> PAGE_SIZE </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">while</span><span style="color:#89DDFF;">(true)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#C792EA;">List</span><span style="color:#89DDFF;">&lt;</span><span style="color:#C792EA;">Integer</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> batchIdList </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">queryList</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">select id FROM `coupon` WHERE expire_date &lt;= #{currentDate} and status = 1 limit #{(pageNo-1) * PAGE_SIZE},#{PAGE_SIZE}</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">);</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">CollectionUtils</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">isEmpty</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;">batchIdList</span><span style="color:#89DDFF;">))</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#82AAFF;">update</span><span style="color:#89DDFF;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">update status = 0 FROM `coupon` where status = 1 and id in #{batchIdList}</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">    pageNo </span><span style="color:#89DDFF;">++;</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span></code></pre></div><h3 id="操作符-优化" tabindex="-1">操作符&lt;&gt;优化 <a class="header-anchor" href="#操作符-优化" aria-label="Permalink to &quot;操作符&lt;&gt;优化&quot;">​</a></h3><p>通常&lt;&gt;操作符无法使用索引，举例如下，查询金额不为100元的订单：<code>select id from orders where amount != 100;</code>如果金额为100的订单极少，这种数据分布严重不均的情况下，有可能使用索引。鉴于这种不确定性，采用union聚合搜索结果，改写方法如下：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> orders </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> amount </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">) </span></span>
<span class="line"><span style="color:#F78C6C;">union all</span></span>
<span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> orders </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> amount </span><span style="color:#89DDFF;">&lt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">and</span><span style="color:#A6ACCD;"> amount </span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><h3 id="or优化" tabindex="-1">OR优化 <a class="header-anchor" href="#or优化" aria-label="Permalink to &quot;OR优化&quot;">​</a></h3><p>在Innodb引擎下or无法使用组合索引，比如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id，product_name </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> orders </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> mobile_no </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">13421800407</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">or</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>OR无法命中mobile<em>no + user</em>id的组合索引，可采用union，如下所示：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id，product_name </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> orders </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> mobile_no </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">13421800407</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">) </span></span>
<span class="line"><span style="color:#F78C6C;">union</span></span>
<span class="line"><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id，product_name </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> orders </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> user_id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>此时id和product_name字段都有索引，查询才最高效。</p><h1 id="如何科学破解慢sql" tabindex="-1">如何科学破解慢SQL <a class="header-anchor" href="#如何科学破解慢sql" aria-label="Permalink to &quot;如何科学破解慢SQL&quot;">​</a></h1><p>今天和大家聊一个常见的问题：慢SQL。</p><p>通过本文你将了解到以下内容：</p><ul><li>慢SQL的危害</li><li>SQL语句的执行过程</li><li>存储引擎和索引的那些事儿</li><li>慢SQL解决之道</li></ul><p>后续均以MySQL默认存储引擎InnoDB为例进行展开，话不多说，开搞！</p><h2 id="_1-慢sql的危害" tabindex="-1">1.慢SQL的危害 <a class="header-anchor" href="#_1-慢sql的危害" aria-label="Permalink to &quot;1.慢SQL的危害&quot;">​</a></h2><p>慢SQL，就是跑得很慢的SQL语句，你可能会问慢SQL会有啥问题吗？试想一个场景：</p><blockquote><p>大白和小黑端午出去玩，机票太贵于是买了高铁，火车站的人真是乌央乌央的。</p></blockquote><blockquote><p>马上检票了，大白和小黑准备去厕所清理下库存，坑位不多，排队的人还真不少。</p></blockquote><blockquote><p>小黑发现其中有3个坑的乘客贼慢，其他2个坑位换了好几波人，这3位坑主就是不出来。</p></blockquote><blockquote><p>等在外面的大伙，心里很是不爽，长期占用公共资源，后面的人没法用。</p></blockquote><blockquote><p>小黑苦笑道：这不就是厕所版的慢SQL嘛！</p></blockquote><p>这是实际生活中的例子，换到MySQL服务器也是一样的，毕竟科技源自生活嘛。</p><p>MySQL服务器的资源(CPU、IO、内存等)是有限的，尤其在高并发场景下需要快速处理掉请求，否则一旦出现慢SQL就会阻塞掉很多正常的请求，造成大面积的失败/超时等。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101433894.png" alt="image-20221210143302732" style="zoom:50%;"><h2 id="_2-sql语句执行过程" tabindex="-1">2.SQL语句执行过程 <a class="header-anchor" href="#_2-sql语句执行过程" aria-label="Permalink to &quot;2.SQL语句执行过程&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101435588.png" alt="image-20221210143525465" style="zoom:67%;"><p>客户端和MySQL服务端的交互过程简介：</p><blockquote><ol><li>客户端发送一条SQL语句给服务端，服务端的连接器先进行账号/密码、权限等验证，有异常直接拒绝请求</li><li>服务端查询缓存，如果SQL语句命中了缓存，则返回缓存中的结果，否则继续处理。</li><li>服务端对SQL语句进行词法解析、语法解析、预处理来检查SQL语句的合法性。</li><li>服务端通过优化器对之前生成的解析树进行优化处理，生成最优的物理执行计划。</li><li>将生成的物理执行计划调用存储引擎的相关接口，进行数据查询和处理。</li><li>处理完成后将结果返回客户端。</li></ol></blockquote><p>客户端和MySQL服务端的交互过程简图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101437875.png" alt="image-20221210143729673" style="zoom:67%;"><p>俗话说&quot;条条大路通罗马&quot;，优化器的作用就是找到这么多路中最优的那一条。</p><p>存储引擎更是决定SQL执行的核心组件，适当了解其中原理十分有益。</p><h2 id="_3-存储引擎和索引的那些事儿" tabindex="-1">3. 存储引擎和索引的那些事儿 <a class="header-anchor" href="#_3-存储引擎和索引的那些事儿" aria-label="Permalink to &quot;3. 存储引擎和索引的那些事儿&quot;">​</a></h2><h3 id="_1-存储引擎" tabindex="-1">1 存储引擎 <a class="header-anchor" href="#_1-存储引擎" aria-label="Permalink to &quot;1 存储引擎&quot;">​</a></h3><p>InnoDB存储引擎(Storage Engine)是MySQL默认之选，所以非常典型。</p><p>存储引擎的主要作用是进行数据的存取和检索，也是真正执行SQL语句的组件。</p><p>InnoDB的整体架构分为两个部分：内存架构和磁盘架构，如图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101437435.png" alt="image-20221210143752126" style="zoom:50%;"><blockquote><p>存储引擎的内容非常多，并不是一篇文章能说清楚的，本文不过多展开，我们在此只需要了解内存架构和磁盘架构的大致组成即可。</p></blockquote><p>InnoDB 引擎是面向行存储的，数据都是存储在磁盘的数据页中，数据页里面按照固定的行格式存储着每一行数据。</p><blockquote><p>行格式主要分为四种类型Compact、Redundant、Dynamic和Compressed，默认为Compact格式。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101438871.png" alt="image-20221210143822710" style="zoom:67%;"><h4 id="磁盘预读机制和局部性原理" tabindex="-1">磁盘预读机制和局部性原理 <a class="header-anchor" href="#磁盘预读机制和局部性原理" aria-label="Permalink to &quot;磁盘预读机制和局部性原理&quot;">​</a></h4><p>当计算机访问一个数据时，不仅会加载当前数据所在的数据页，还会将当前数据页相邻的数据页一同加载到内存，磁盘预读的长度一般为页的整倍数，从而有效降低磁盘IO的次数。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101438124.png" alt="image-20221210143844973" style="zoom:50%;"><h4 id="磁盘和内存的交互" tabindex="-1">磁盘和内存的交互 <a class="header-anchor" href="#磁盘和内存的交互" aria-label="Permalink to &quot;磁盘和内存的交互&quot;">​</a></h4><p>MySQL中磁盘的数据需要被交换到内存，才能完成一次SQL交互，大致如图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101439051.png" alt="image-20221210143903911" style="zoom:50%;"><ul><li>扇区是硬盘的读写的基本单位，通常情况下每个扇区的大小是 512B</li><li>磁盘块文件系统读写数据的最小单位，相邻的扇区组合在一起形成一个块，一般是4KB</li><li>页是内存的最小存储单位，页的大小通常为磁盘块大小的 2^n 倍</li><li>InnoDB页面的默认大小是16KB，是数倍个操作系统的页</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101439815.png" alt="image-20221210143926681" style="zoom:50%;"><h4 id="随机磁盘io" tabindex="-1">随机磁盘IO <a class="header-anchor" href="#随机磁盘io" aria-label="Permalink to &quot;随机磁盘IO&quot;">​</a></h4><p>MySQL的数据是一行行存储在磁盘上的，并且这些数据并非物理连续地存储，这样的话要查找数据就无法避免随机在磁盘上读取和写入数据。</p><p>对于MySQL来说，当出现大量磁盘随机IO时，大部分时间都被浪费到寻道上，磁盘呼噜呼噜转，就是传输不了多少数据。</p><blockquote><p>一次磁盘访问由三个动作组成：</p><ul><li>寻道：磁头移动定位到指定磁道</li><li>旋转：等待指定扇区从磁头下旋转经过</li><li>数据传输：数据在磁盘与内存之间的实际传输</li></ul></blockquote><p>对于存储引擎来说，如何有效降低随机IO是个非常重要的问题。</p><h3 id="_2-索引" tabindex="-1">2 索引 <a class="header-anchor" href="#_2-索引" aria-label="Permalink to &quot;2 索引&quot;">​</a></h3><p>可以实现增删改查的数据结构非常多，包括：哈希表、二叉搜索树、AVL、红黑树、B树、B+树等，这些都是可以作为索引的候选数据结构。</p><p>结合MySQL的实际情况：磁盘和内存交互、随机磁盘IO、排序和范围查找、增删改的复杂度等等，综合考量之下B+树脱颖而出。</p><p>B+树作为多叉平衡树，对于范围查找和排序都可以很好地支持，并且更加矮胖，访问数据时的平均磁盘IO次数取决于树的高度，因此B+树可以让磁盘的查找次数更少。</p><p>在InnoDB中B+树的高度一般都在2~4层，并且根节点常驻内存中，也就是说查找某值的行记录时最多只需要1~3次磁盘I/O操作。</p><p>MyISAM是将数据和索引分开存储的，InnoDB存储引擎的数据和索引没有分开存储，这也就是为什么有人说Innodb索引即数据，数据即索引，如图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101439824.png" alt="image-20221210143956642" style="zoom:50%;"><p>说到InnoDB的数据和索引的存储，就提到一个名词：<strong>聚集索引</strong>。</p><h4 id="聚集索引" tabindex="-1">聚集索引 <a class="header-anchor" href="#聚集索引" aria-label="Permalink to &quot;聚集索引&quot;">​</a></h4><p>聚集索引将索引和数据完美地融合在一起，是每个Innodb表都会有的一个特殊索引，一般来说是借助于表的主键来构建的B+树。</p><p>假设我们有student表，将id作为主键索引，那么聚集索引的B+树结构，如图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101440795.png" alt="image-20221210144013633" style="zoom:67%;"><ul><li>非叶子节点不存数据，只有主键和相关指针</li><li>叶子节点包含主键、行数据、指针</li><li>叶子节点之间由双向指针串联形成有序双向链表，叶子节点内部也是有序的</li></ul><p>聚集索引按照如下规则创建：</p><ul><li>有主键时InnoDB利用主键来生成</li><li>没有主键，InnoDB会选择一个非空的唯一索引来创建</li><li>无主键且非NULL唯一索引时，InnoDB会隐式创建一个自增的列来创建</li></ul><p>假如我们要查找id=10的数据，大致过程如下：</p><ul><li>索引的根结点在内存中，10&gt;9 因此找到P3指针</li><li>P3指向的数据并没有在内存中，因此产生1次磁盘IO读取磁盘块3到内存</li><li>在内存中对磁盘块3进行二分查找，找到ID=9的全部值</li></ul><h4 id="非聚集索引" tabindex="-1">非聚集索引 <a class="header-anchor" href="#非聚集索引" aria-label="Permalink to &quot;非聚集索引&quot;">​</a></h4><p>非聚集索引的叶子节点中存放的是二级索引值和主键键值，非叶子节点和叶子节点都没有存储整行数据值。</p><p>假设我们有student表，将name作为二级索引，那么非聚集索引的B+树结构，如图：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/wAkAIFs11qaMZss2ia9Bx0yJNIfuKRhiawcfyMslw9iayvVYUmA00ia3xQgP1NRefw2oIWBRhSbqomtK8icuibOC7eiaQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>由于非聚集索引的叶子节点没有存储行数据，如果通过非聚集索引来查找非二级索引值，需要分为两步：</p><ul><li>第一：通过非聚集索引的叶子节点来确定数据行对应的主键</li><li>第二：通过相应的主键值在聚集索引中查询到对应的行记录</li></ul><p>我们把通过非聚集索引找到主键值，再根据主键值从聚集索引找对于行数据的过程称为：<strong>回表查询</strong>。</p><p>换句话说：select * from student where name = &#39;Bob&#39; 将产生回表查询，因为在name索引的叶子节点没有其他值，只能从聚集索引获得。</p><p>所以如果查找的字段在非聚集索引就可以完成，就可以避免一次回表过程，这种称为：覆盖索引，所以select * 并不是好习惯，需要什么拿什么就好。</p><p>假如我们要查找name=Tom的记录的所有值，大致过程如下：</p><ul><li>从非聚集索引开始，根节点在内存中，按照name的字典序找到P3指针</li><li>P3指针所指向的磁盘块不在内存中，产生1次磁盘IO加载到内存</li><li>在内存中对磁盘块3的数据进行搜索，获得name=tom的记录的主键值为4</li><li>根据主键值4从聚集索引的根节点中获得P2指针</li><li>P2指针所指向的磁盘块不在内存中，产生第2次磁盘IO加载到内存</li><li>将上一步获得的数据，在内存中进行二分查找获得全部行数据</li></ul><p>上述查询就包含了一次回表过程，因此性能比主键查询慢了一倍，因此尽量使用主键查询，一次完事。</p><h2 id="_4-慢sql解决思路" tabindex="-1">4. 慢SQL解决思路 <a class="header-anchor" href="#_4-慢sql解决思路" aria-label="Permalink to &quot;4. 慢SQL解决思路&quot;">​</a></h2><p>出现慢SQL的原因很多，我们抛开单表数亿记录和无索引的特殊情况，来讨论一些更有普遍意义的慢SQL原因和解决之道。</p><p>我们从两个方面来进行阐述：</p><ul><li>数据库表索引设置不合理</li><li>SQL语句有问题，需要优化</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101444616.png" alt="image-20221210144456493" style="zoom:67%;"><h3 id="_1-索引设置原则" tabindex="-1">1 索引设置原则 <a class="header-anchor" href="#_1-索引设置原则" aria-label="Permalink to &quot;1 索引设置原则&quot;">​</a></h3><p>程序员的角度和存储引擎的角度是不一样的，索引写的好，SQL跑得快。</p><ul><li><strong>索引区分度低</strong></li></ul><p>假如表中有1000w记录，其中有status字段表示状态，可能90%的数据status=1，可以不将status作为索引，因为其对数据记录区分度很低。</p><ul><li><strong>切忌过多创建索引</strong></li></ul><p>每个索引都需要占用磁盘空间，修改表数据时会对索引进行更新，索引越多，更新越复杂。</p><blockquote><p>因为每添加一个索引，.ibd文件中就需要多维护一个B+Tree索引树，如果某一个table中存在10个索引，那么就需要维护10棵B+Tree，写入效率会降低，并且会浪费磁盘空间。</p></blockquote><ul><li><strong>常用查询字段建索引</strong></li></ul><p>如果某个字段经常用来做查询条件，那么该字段的查询速度会影响整个表的查询速度，属于热门字段，为其建立索引非常必要。</p><ul><li><strong>常排序/分组/去重字段建索引</strong></li></ul><p>对于需要经常使用ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段建立索引，可以有效借助B+树的特性来加速执行。</p><ul><li><strong>主键和外键建索引</strong></li></ul><p>主键可以用来创建聚集索引，外键也是唯一的且常用于表关联的字段，也需要建索引来提高性能。</p><h3 id="_2-sql的优化" tabindex="-1">2 SQL的优化 <a class="header-anchor" href="#_2-sql的优化" aria-label="Permalink to &quot;2 SQL的优化&quot;">​</a></h3><p>如果数据库表的索引设置比较合理，SQL语句书写不当会造成索引失效，甚至造成全表扫描，迅速拉低性能。</p><h4 id="索引失效" tabindex="-1">索引失效 <a class="header-anchor" href="#索引失效" aria-label="Permalink to &quot;索引失效&quot;">​</a></h4><p>我们在写SQL的时候在某些情况下会出现索引失效的情况：</p><ul><li><strong>对索引使用函数</strong></li></ul><blockquote><p>select id from std upper(name) = &#39;JIM&#39;;</p></blockquote><ul><li><strong>对索引进行运算</strong></li></ul><blockquote><p>select id from std where id+1=10;</p></blockquote><ul><li><strong>对索引使用&lt;&gt; 、not in 、not exist、!=</strong></li></ul><blockquote><p>select id from std where name != &#39;jim&#39;;</p></blockquote><ul><li><strong>对索引进行前导模糊查询</strong></li></ul><blockquote><p>select id from std name like &#39;%jim&#39;;</p></blockquote><ul><li><strong>隐式转换会导致不走索引</strong></li></ul><blockquote><p>比如：字符串类型索引字段不加引号，select id from std name = 100;保持变量类型与字段类型一致</p></blockquote><ul><li><strong>非索引字段的or连接</strong></li></ul><blockquote><p>并不是所有的or都会使索引失效，如果or连接的所有字段都设置了索引，是会走索引的，一旦有一个字段没有索引，就会走全表扫描。</p></blockquote><ul><li><strong>联合索引仅包含复合索引非前置列</strong></li></ul><blockquote><p>联合索引包含key1，key2，key3三列，但SQL语句没有key1，根据联合索引的最左匹配原则，不会走联合索引。 select name from table where key2=1 and key3=2;</p></blockquote><h4 id="好的建议" tabindex="-1">好的建议 <a class="header-anchor" href="#好的建议" aria-label="Permalink to &quot;好的建议&quot;">​</a></h4><ul><li><strong>使用连接代替子查询</strong></li></ul><blockquote><p>对于数据库来说，在绝大部分情况下，连接会比子查询更快，使用连接的方式，MySQL优化器一般可以生成更佳的执行计划，更高效地处理查询 而子查询往往需要运行重复的查询，子查询生成的临时表上也没有索引， 因此效率会更低。</p></blockquote><ul><li><strong>LIMIT偏移量过大的优化</strong></li></ul><blockquote><p>禁止分页查询偏移量过大，如limit 100000,10</p></blockquote><ul><li><strong>使用覆盖索引</strong> 减少select * 借助覆盖索引，减少回表查询次数。</li><li><strong>多表关联查询时，小表在前，大表在后</strong></li></ul><blockquote><p>在MySQL中，执行from后的表关联查询是从左往右执行的，第一张表会涉及到全表扫描，所以将小表放在前面，先扫小表，扫描快效率较高，在扫描后面的大表，或许只扫描大表的前100行就符合返回条件并return了。</p></blockquote><ul><li><strong>调整Where字句中的连接顺序</strong></li></ul><blockquote><p>MySQL采用从左往右的顺序解析where子句，可以将过滤数据多的条件放在前面，最快速度缩小结果集。</p></blockquote><ul><li><strong>使用小范围事务，而非大范围事务</strong></li><li><strong>遵循最左匹配原则</strong></li><li><strong>使用联合索引，而非建立多个单独索引</strong></li></ul><h3 id="_3-慢sql的分析" tabindex="-1">3 慢SQL的分析 <a class="header-anchor" href="#_3-慢sql的分析" aria-label="Permalink to &quot;3 慢SQL的分析&quot;">​</a></h3><p>在分析慢SQL之前需要通过MySQL进行相关设置：</p><ul><li>开启慢SQL日志</li><li>设置慢SQL的执行时间阈值</li></ul><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">开启：</span><span style="color:#F78C6C;">SET</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">GLOBAL</span><span style="color:#A6ACCD;"> slow_query_log </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">开启状态：SHOW VARIABLES </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%slow_query_log%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">设置阈值：</span><span style="color:#F78C6C;">SET</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">GLOBAL</span><span style="color:#A6ACCD;"> long_query_time</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">查看阈值：SHOW </span><span style="color:#F78C6C;">GLOBAL</span><span style="color:#A6ACCD;"> VARIABLES </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">long_query_time%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><h4 id="explain分析sql" tabindex="-1">explain分析SQL <a class="header-anchor" href="#explain分析sql" aria-label="Permalink to &quot;explain分析SQL&quot;">​</a></h4><p>explain命令只需要加在select之前即可，例如:</p><blockquote><p>explain select * from std where id &lt; 100;</p></blockquote><p>该命令会展示sql语句的详细执行过程，帮助我们定位问题，网上关于explain的用法和讲解很多，本文不再展开。</p><h1 id="尽量避免使用-in-和-not-in" tabindex="-1">尽量避免使用 IN 和 NOT IN <a class="header-anchor" href="#尽量避免使用-in-和-not-in" aria-label="Permalink to &quot;尽量避免使用 IN 和 NOT IN&quot;">​</a></h1><p>IN 和 NOT IN 是比较常用的关键字，为什么要尽量避免呢？</p><h2 id="_1、效率低" tabindex="-1">1、效率低 <a class="header-anchor" href="#_1、效率低" aria-label="Permalink to &quot;1、效率低&quot;">​</a></h2><p>项目中遇到这么个情况：t1表 和 t2表 都是150w条数据，600M的样子，都不算大。</p><p>但是这样一句查询 ↓</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> t1 </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> phone </span><span style="color:#F78C6C;">not</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> phone </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> t2)</span></span></code></pre></div><p>直接就把我跑傻了。。。</p><p>十几分钟，检查了一下 phone在两个表都建了索引，字段类型也是一样的。原来 not in 是不能命中索引的。。。。</p><p>改成 NOT EXISTS 之后查询 20s ，效率真的差好多。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> t1</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">not</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">EXISTS</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> phone </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> t2  </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> t1.phone </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">t2.phone)</span></span></code></pre></div><h2 id="_2、容易出现问题-或查询结果有误" tabindex="-1">2、容易出现问题，或查询结果有误 <a class="header-anchor" href="#_2、容易出现问题-或查询结果有误" aria-label="Permalink to &quot;2、容易出现问题，或查询结果有误&quot;">​</a></h2><p>以 IN 为例。建两个表：test1 和 test2</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test1</span><span style="color:#A6ACCD;"> (id1 </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">create</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">test2</span><span style="color:#A6ACCD;"> (id2 </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> test1 (id1) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">),(</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">),(</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> test2 (id2) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">),(</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>我想要查询，在test2中存在的 test1中的id 。</p><p>使用 IN 的一般写法是：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test2)</span></span></code></pre></div><p>结果是：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/TNUwKhV0JpRCHeXZA1QmGEhziccBictFUF9tc8voEhaibDD8QqjjIgT1f1mKqxBKyj4lcl1qVHic73Lb5iaziccBV2Fw/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>OK 木有问题！但是如果我一时手滑，写成了：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test2)</span></span></code></pre></div><p>不小心把id2写成id1了 ，会怎么样呢?结果是：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/TNUwKhV0JpRCHeXZA1QmGEhziccBictFUFMgS6uicEGibkqgbLiapnskVS20OlBoQdrtzMLbde02gzbcuT7uRzxTHAA/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>EXCUSE ME！为什么不报错？</p><p>单独查询 <code>select id1 from test2</code> 是一定会报错: 消息 207，级别 16，状态 1，第 11 行 列名 &#39;id1&#39; 无效。</p><p>然而使用了IN的子查询就是这么敷衍，直接查出 1 2 3</p><p>这仅仅是容易出错的情况，自己不写错还没啥事儿，下面来看一下 NOT IN 直接查出错误结果的情况：</p><p>给test2插入一个空值：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">insert into</span><span style="color:#A6ACCD;"> test2 (id2) </span><span style="color:#F78C6C;">values</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>我想要查询，在test2中不存在的 test1中的 id 。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">not</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test2)</span></span></code></pre></div><p>结果是：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/TNUwKhV0JpRCHeXZA1QmGEhziccBictFUF7cuwbJgaTFIdphDs6LbOIetkiaibiaQUibf1LNHG4ZsTeCbib7MNdHNNNow/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>空白！显然这个结果不是我们想要的。我们想要3。为什么会这样呢？</p><p>原因是：NULL不等于任何非空的值啊！如果id2只有1和2， 那么3&lt;&gt;1 且 3&lt;&gt;2 所以3输出了，但是 id2包含空值，那么 3也不等于NULL 所以它不会输出。</p><blockquote><p>跑题一句：建表的时候最好不要允许含空值，否则问题多多。</p></blockquote><h2 id="_3、如何替换-in" tabindex="-1">3、如何替换 IN <a class="header-anchor" href="#_3、如何替换-in" aria-label="Permalink to &quot;3、如何替换 IN&quot;">​</a></h2><h3 id="_1、用-exists-或-not-exists-代替" tabindex="-1">1、用 EXISTS 或 NOT EXISTS 代替 <a class="header-anchor" href="#_1、用-exists-或-not-exists-代替" aria-label="Permalink to &quot;1、用 EXISTS 或 NOT EXISTS 代替&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">EXISTS</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test2  </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> id1 )</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;">  </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NOT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">EXISTS</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test2  </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> id1 )</span></span></code></pre></div><h3 id="_2、用join-代替" tabindex="-1">2、用JOIN 代替 <a class="header-anchor" href="#_2、用join-代替" aria-label="Permalink to &quot;2、用JOIN 代替&quot;">​</a></h3><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1 </span><span style="color:#F78C6C;">INNER JOIN</span><span style="color:#A6ACCD;"> test2 </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> id1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> id1 </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> test1</span></span>
<span class="line"><span style="color:#F78C6C;">LEFT JOIN</span><span style="color:#A6ACCD;"> test2 </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> id1</span></span>
<span class="line"><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id2 </span><span style="color:#F78C6C;">IS</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span></span></code></pre></div><p>妥妥的没有问题了</p><blockquote><p>PS：那我们死活都不能用 IN 和 NOT IN 了么？并没有，一位大神曾经说过，如果是确定且有限的集合时，可以使用。如 IN （0，1，2）。</p></blockquote><h1 id="为什么-select-效率低" tabindex="-1">为什么 SELECT * 效率低 <a class="header-anchor" href="#为什么-select-效率低" aria-label="Permalink to &quot;为什么 SELECT * 效率低&quot;">​</a></h1><p>无论在工作还是面试中，关于SQL中不要用“SELECT *”，都是大家听烂了的问题，虽说听烂了，但普遍理解还是在很浅的层面，并没有多少人去追根究底，探究其原理。</p><h2 id="效率低的原因" tabindex="-1">效率低的原因 <a class="header-anchor" href="#效率低的原因" aria-label="Permalink to &quot;效率低的原因&quot;">​</a></h2><p>先看一下最新《阿里java开发手册（泰山版）》中 MySQL 部分描述： 【强制】在表查询中，一律不要使用 * 作为查询的字段列表，需要哪些字段必须明确写明。说明：</p><blockquote><ul><li>增加查询分析器解析成本。</li><li>增减字段容易与 resultMap 配置不一致。</li><li>无用字段增加网络 消耗，尤其是 text 类型的字段。</li></ul></blockquote><p>开发手册中比较概括的提到了几点原因，让我们深入一些看看：</p><h3 id="_1-不需要的列会增加数据传输时间和网络开销" tabindex="-1">1. 不需要的列会增加数据传输时间和网络开销 <a class="header-anchor" href="#_1-不需要的列会增加数据传输时间和网络开销" aria-label="Permalink to &quot;1. 不需要的列会增加数据传输时间和网络开销&quot;">​</a></h3><blockquote><ul><li>用“SELECT * ”数据库需要解析更多的对象、字段、权限、属性等相关内容，在 SQL 语句复杂，硬解析较多的情况下，会对数据库造成沉重的负担。</li><li>增大网络开销；* 有时会误带上如log、IconMD5之类的无用且大文本字段，数据传输size会几何增涨。如果DB和应用程序不在同一台机器，这种开销非常明显。</li><li>即使 mysql 服务器和客户端是在同一台机器上，使用的协议还是 tcp，通信也是需要额外的时间。</li></ul></blockquote><h3 id="_2-对于无用的大字段-如-varchar、blob、text-会增加-io-操作" tabindex="-1">2. 对于无用的大字段，如 varchar、blob、text，会增加 io 操作 <a class="header-anchor" href="#_2-对于无用的大字段-如-varchar、blob、text-会增加-io-操作" aria-label="Permalink to &quot;2. 对于无用的大字段，如 varchar、blob、text，会增加 io 操作&quot;">​</a></h3><blockquote><p>准确来说，长度超过 728 字节的时候，会先把超出的数据序列化到另外一个地方，因此读取这条记录会增加一次 io 操作。（MySQL InnoDB）</p></blockquote><h3 id="_3-失去mysql优化器-覆盖索引-策略优化的可能性" tabindex="-1">3. 失去MySQL优化器“覆盖索引”策略优化的可能性 <a class="header-anchor" href="#_3-失去mysql优化器-覆盖索引-策略优化的可能性" aria-label="Permalink to &quot;3. 失去MySQL优化器“覆盖索引”策略优化的可能性&quot;">​</a></h3><blockquote><p>SELECT * 杜绝了覆盖索引的可能性，而基于MySQL优化器的“覆盖索引”策略又是速度极快，效率极高，业界极为推荐的查询优化方式。</p></blockquote><p>例如，有一个表为t(a,b,c,d,e,f)，其中，a为主键，b列有索引。</p><blockquote><p>那么，在磁盘上有两棵 B+ 树，即聚集索引和辅助索引（包括单列索引、联合索引），分别保存(a,b,c,d,e,f)和(a,b)，如果查询条件中where条件可以通过b列的索引过滤掉一部分记录，查询就会先走辅助索引，如果用户只需要a列和b列的数据，直接通过辅助索引就可以知道用户查询的数据。</p></blockquote><blockquote><p>如果用户使用select *，获取了不需要的数据，则首先通过辅助索引过滤数据，然后再通过聚集索引获取所有的列，这就多了一次b+树查询，速度必然会慢很多。</p></blockquote><blockquote><p>由于辅助索引的数据比聚集索引少很多，很多情况下，通过辅助索引进行覆盖索引（通过索引就能获取用户需要的所有列），都不需要读磁盘，直接从内存取，而聚集索引很可能数据在磁盘（外存）中（取决于buffer pool的大小和命中率），这种情况下，一个是内存读，一个是磁盘读，速度差异就很显著了，几乎是数量级的差异。</p></blockquote><h2 id="索引知识延申" tabindex="-1">索引知识延申 <a class="header-anchor" href="#索引知识延申" aria-label="Permalink to &quot;索引知识延申&quot;">​</a></h2><p>上面提到了辅助索引，在MySQL中辅助索引包括单列索引、联合索引（多列联合），单列索引就不再赘述了，这里提一下联合索引的作用。</p><h3 id="联合索引-a-b-c" tabindex="-1">联合索引 (a,b,c) <a class="header-anchor" href="#联合索引-a-b-c" aria-label="Permalink to &quot;联合索引 (a,b,c)&quot;">​</a></h3><blockquote><p>联合索引 (a,b,c) 实际建立了 (a)、(a,b)、(a,b,c) 三个索引</p></blockquote><blockquote><p>我们可以将组合索引想成书的一级目录、二级目录、三级目录，如index(a,b,c)，相当于a是一级目录，b是一级目录下的二级目录，c是二级目录下的三级目录。要使用某一目录，必须先使用其上级目录，一级目录除外。</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/VbfrPx9GoVeZQJUaYu0X0xHZpLRjuzxQ3RraCcbr7Ee43MrFOPiaYbyAGg7vicN009LL7YNicicpsa9ZJkiaJJAYS3A/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h2 id="联合索引的优势" tabindex="-1">联合索引的优势 <a class="header-anchor" href="#联合索引的优势" aria-label="Permalink to &quot;联合索引的优势&quot;">​</a></h2><h3 id="_1-减少开销" tabindex="-1">1） 减少开销 <a class="header-anchor" href="#_1-减少开销" aria-label="Permalink to &quot;1） 减少开销&quot;">​</a></h3><blockquote><p>建一个联合索引 (a,b,c) ，实际相当于建了 (a)、(a,b)、(a,b,c) 三个索引。每多一个索引，都会增加写操作的开销和磁盘空间的开销。对于大量数据的表，使用联合索引会大大的减少开销！</p></blockquote><h3 id="_2-覆盖索引" tabindex="-1">2）覆盖索引 <a class="header-anchor" href="#_2-覆盖索引" aria-label="Permalink to &quot;2）覆盖索引&quot;">​</a></h3><p>对联合索引 (a,b,c)，如果有如下 sql 的，</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">SELECT a,b,c from table where a=&#39;xx&#39; and b = &#39;xx&#39;;</span></span></code></pre></div><blockquote><p>那么 MySQL 可以直接通过遍历索引取得数据，而无需回表，这减少了很多的随机 io 操作。减少 io 操作，特别是随机 io 其实是 DBA 主要的优化策略。所以，在真正的实际应用中，覆盖索引是主要的提升性能的优化手段之一。</p></blockquote><h3 id="_3-效率高" tabindex="-1">3）效率高 <a class="header-anchor" href="#_3-效率高" aria-label="Permalink to &quot;3）效率高&quot;">​</a></h3><p>索引列多，通过联合索引筛选出的数据越少。比如有 1000W 条数据的表，有如下SQL:</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select col1,col2,col3 from table where col1=1 and col2=2 and col3=3;</span></span></code></pre></div><p>假设：假设每个条件可以筛选出 10% 的数据。</p><ul><li>A. 如果只有单列索引，那么通过该索引能筛选出 1000W 10%=100w 条数据，然后再回表从 100w 条数据中找到符合 col2=2 and col3= 3 的数据，然后再排序，再分页，以此类推（递归）；</li><li>B. 如果是（col1,col2,col3）联合索引，通过三列索引筛选出 1000w 10% 10% *10%=1w，效率提升可想而知！</li></ul><h3 id="_4-索引是建的越多越好吗" tabindex="-1">4) 索引是建的越多越好吗 <a class="header-anchor" href="#_4-索引是建的越多越好吗" aria-label="Permalink to &quot;4)  索引是建的越多越好吗&quot;">​</a></h3><p>答案自然是否定的</p><blockquote><ul><li>数据量小的表不需要建立索引，建立会增加额外的索引开销</li><li>不经常引用的列不要建立索引，因为不常用，即使建立了索引也没有多大意义</li><li>经常频繁更新的列不要建立索引，因为肯定会影响插入或更新的效率</li><li>数据重复且分布平均的字段，因此他建立索引就没有太大的效果（例如性别字段，只有男女，不适合建立索引）</li><li>数据变更需要维护索引，意味着索引越多维护成本越高。</li><li>更多的索引也需要更多的存储空间</li></ul></blockquote><h1 id="隐式转换带来的cpu飙升" tabindex="-1">隐式转换带来的cpu飙升 <a class="header-anchor" href="#隐式转换带来的cpu飙升" aria-label="Permalink to &quot;隐式转换带来的cpu飙升&quot;">​</a></h1><h2 id="问题背景" tabindex="-1">问题背景 <a class="header-anchor" href="#问题背景" aria-label="Permalink to &quot;问题背景&quot;">​</a></h2><p>本来是一个平静而美好的下午，其他部门的同事要一份数据报表临时汇报使用，因为系统目前没有这个维度的功能，所以需要写个SQL马上出一下，一个同事接到这个任务，于是开始在测试环境拼装这条 SQL，刚过了几分钟，同事已经自信的写好了这条SQL，于是拿给DBA，到线上跑一下，用客户端工具导出Excel 就好了，毕竟是临时方案嘛。</p><p>就在SQL执行了之后，意外发生了，先是等了一下，发现还没执行成功，猜测可能是数据量大的原因，但是随着时间滴滴答答流逝，逐渐意识到情况不对了，一看监控，CPU已经上去了，但是线上数据量虽然不小，也不至于跑成这样吧，眼看着要跑死了，赶紧把这个事务结束掉了。</p><p>什么原因呢？查询的条件和 join 连接的字段基本都有索引，按道理不应该这样啊，于是赶紧把SQL拿下来，也没看出什么问题，于是限制查询条数再跑了一次，很快出结果了，但是结果却大跌眼镜，出来的查询结果并不是预期的。</p><p>经过一番检查之后，最终发现了问题所在，是 join 连接中有一个字段写错了，因为这两个字段有一部分名称是相同的，于是智能的 SQL 客户端给出了提示，顺手就给敲上去了。但是接下来，更让人迷惑了，因为要连接的字段是 int 类型，而写错的这个字段是 varchar 类型，难道不应该报错吗？怎么还能正常执行，并且还有预期外的查询结果？</p><p>难道是 MySQL 有 bug 了，必须要研究一下了。</p><h2 id="复现当时的场景" tabindex="-1">复现当时的场景 <a class="header-anchor" href="#复现当时的场景" aria-label="Permalink to &quot;复现当时的场景&quot;">​</a></h2><p>假设有两张表，这两张表的结构和数据是下面这样的。</p><p>第一张 <code>user</code>表。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">user</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">50</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 </span><span style="color:#C792EA;">COLLATE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8_bin;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">user</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">张三</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">28</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2022-09-06 07:40:56</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2022-09-06 07:40:59</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171035441.png" alt="image-20221217103523300" style="zoom:80%;"><p>第二张 <code>order</code>表</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">CREATE</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">TABLE</span><span style="color:#A6ACCD;"> `</span><span style="color:#82AAFF;">order</span><span style="color:#A6ACCD;">` (</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">NOT NULL</span><span style="color:#A6ACCD;"> AUTO_INCREMENT,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">user_id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">int</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">11</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order_code</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">64</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">money</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">decimal</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">20</span><span style="color:#A6ACCD;">,</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">title</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">255</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">COLLATE</span><span style="color:#A6ACCD;"> utf8_bin </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">create_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">update_time</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">datetime</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">PRIMARY KEY</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">) ENGINE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">InnoDB AUTO_INCREMENT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> CHARSET</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8 </span><span style="color:#C792EA;">COLLATE</span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;">utf8_bin;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">2</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">1d90530e-6ada-47c1-b2fa-adba4545aabd</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">100</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">xxx购买两件商品</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2022-09-06 07:42:25</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">2022-09-06 07:42:27</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p><img src="https://mmbiz.qpic.cn/mmbiz_png/iaWSDo4TfyZgbMHOd9kxozlMumXFVbj32spBNmWCQ8iajNMXyxbVsPnM8SKMEzCBE0ia2icvbs1ciaP6qoD3yQtL7Mg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>目的是查看所有用户的 order 记录，假设数据量比较少，可以直接查，不考虑性能问题。</p><p>本来的 SQL 语句应该是这样子的，查询 <code>order</code>表中用户id<code>user_id</code>在<code>user</code>表的记录。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> o.</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">user</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> u </span></span>
<span class="line"><span style="color:#F78C6C;">left JOIN</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> o </span></span>
<span class="line"><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> u.id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> o.user_id;</span></span></code></pre></div><p>但是呢，因为手抖，将 on 后面的条件写成了 <code>u.id = o.order_code</code>，完全关联错误，这两个字段完全没有联系，而且<code>u.id</code>是 int 类型，<code>o.order_code</code>是<code>varchar</code>类型。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> o.</span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">user</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> u </span></span>
<span class="line"><span style="color:#F78C6C;">left JOIN</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> o </span></span>
<span class="line"><span style="color:#F78C6C;">on</span><span style="color:#A6ACCD;"> u.id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> o.order_code;</span></span></code></pre></div><p>这样的话， 当我们执行这条语句的时候，会不会查出数据来呢？</p><p>我的第一感觉是，不仅不会查出数据，而且还会报错，因为连接的这两个字段类型都不一样，值更不一样。</p><p>结果却被啪啪打脸，不仅没有报错，而且还查出了数据。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/iaWSDo4TfyZgbMHOd9kxozlMumXFVbj32TToCUwUQicfjRfDnicGSrWIDAb8J5WkJbuc9tb2mDnicMiacwVMeMs3UtA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>可以把这个问题简化一下，简化成下面这条语句，同样也会出现问题。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> order_code </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p><img src="https://mmbiz.qpic.cn/mmbiz_png/iaWSDo4TfyZgbMHOd9kxozlMumXFVbj32r7RKWibTdOICjS8kk4JgooIn2CU0C95TvyeLBMIyAo0cu4Z1mohm5TA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>明明这条记录的 order_code 字段的值是 <code>1d90530e-6ada-47c1-b2fa-adba4545aabd</code>，怎么用 <code>order_code=1</code>的条件就把它给查出来了。</p><h2 id="根源所在" tabindex="-1">根源所在 <a class="header-anchor" href="#根源所在" aria-label="Permalink to &quot;根源所在&quot;">​</a></h2><p>相信有的同学已经猜出来了，这里是 MySQL 进行了隐式转换，由于查询条件后面跟的查询值是整型的，所以 MySQL 将 <code>order_code</code>字段进行了字符串到整数类型的转换，而转换后的结果正好是 <code>1</code>。</p><p>通过 <code>cast</code>函数转换验证一下结果。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">cast</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">1d90530e-6ada-47c1-b2fa-adba4545aabd</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> unsigned);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171037789.png" alt="image-20221217103737667" style="zoom:67%;"><p>再用两条 SQL 看一下字符串到整数类型转换的规则。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">cast</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">223kkk</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> unsigned);</span></span>
<span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">cast</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">k223kkk</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> unsigned);</span></span></code></pre></div><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171037886.png" alt="image-20221217103759658" style="zoom:50%;"><p><code>223kkk</code>转换后的结果是 <code>223</code>，而<code>k223kkk</code>转换后的结果是0。总结一下，转换的规则是：</p><p>1、从字符串的左侧开始向右转换，遇到非数字就停止；</p><p>2、如果第一个就是非数字，最后的结果就是0；</p><h2 id="隐式转换的规则" tabindex="-1">隐式转换的规则 <a class="header-anchor" href="#隐式转换的规则" aria-label="Permalink to &quot;隐式转换的规则&quot;">​</a></h2><p>当操作符与不同类型的操作数一起使用的时候，就会发生隐式转换。</p><p>例如算数运算符的前后是不同类型时，会将非数字类型转换为数字，比如 &#39;5a&#39;+2，就会将<code>5a</code>转换为数字类型，然后和2相加，最后的结果就是 7 。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171038051.png" alt="image-20221217103824977" style="zoom:67%;"><p>再比如 <code>concat</code>函数是连接两个字符串的，当此函数的参数出现非字符串类型时，就会将其转换为字符串，例如concat(88,&#39;就是发&#39;)，最后的结果就是 <code>88就是发</code>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171038543.png" alt="image-20221217103841441" style="zoom:50%;"><p>MySQL 官方文档有以下几条关于隐式转换的规则：</p><blockquote><p>1、两个参数至少有一个是 NULL 时，比较的结果也是 NULL，例外是使用 &lt;=&gt; 对两个 NULL 做比较时会返回 1，这两种情况都不需要做类型转换；也就是两个参数中如果只有一个是NULL，则不管怎么比较结果都是 NULL，而两个 NULL 的值不管是判断大于、小于或等于，其结果都是1。</p></blockquote><blockquote><p>2、两个参数都是字符串，会按照字符串来比较，不做类型转换；</p></blockquote><blockquote><p>3、两个参数都是整数，按照整数来比较，不做类型转换；</p></blockquote><blockquote><p>4、十六进制的值和非数字做比较时，会被当做二进制字符串；</p></blockquote><p>例如下面这条语句，查询 user 表中name字段是 0x61 的记录，<code>0x</code>是16进制写法，其对应的字符串是英文的 &#39;a&#39;，也就是它对应的 ASCII 码。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> 0x61;</span></span></code></pre></div><p>所以，上面这条语句其实等同于下面这条</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">a</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>可以用 <code>select 0x61;</code>验证一下。</p><blockquote><p>5、有一个参数是 TIMESTAMP 或 DATETIME，并且另外一个参数是常量，常量会被转换为 时间戳；</p></blockquote><p>例如下面这两条SQL，都是将条件后面的值转换为时间戳再比较了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212171040223.png" alt="image-20221217104016044" style="zoom:67%;"><blockquote><p>6、有一个参数是 decimal 类型，如果另外一个参数是 decimal 或者整数，会将整数转换为 decimal 后进行比较，如果另外一个参数是浮点数（一般默认是 double），则会把 decimal 转换为浮点数进行比较；</p></blockquote><blockquote><p>在不同的数值类型之间，总是会向精度要求更高的那一个类型转换，但是有一点要注意，在MySQL 中浮点数的精度只有53 bit，超过53bit之后的话，如果后面1位是1就进位，如果是0就直接舍弃。所以超大浮点数在比较的时候其实只是取的近似值。</p></blockquote><blockquote><p>7、所有其他情况下，两个参数都会被转换为浮点数再进行比较；</p></blockquote><blockquote><p>如果不符合上面6点规则，则统一转成浮点数再进行运算</p></blockquote><h2 id="避免进行隐式转换" tabindex="-1">避免进行隐式转换 <a class="header-anchor" href="#避免进行隐式转换" aria-label="Permalink to &quot;避免进行隐式转换&quot;">​</a></h2><p>我们在平时的开发过程中，尽量要避免隐式转换，因为一旦发生隐式转换除了会降低性能外， 还有很大可能会出现不期望的结果，就像我最开始遇到的那个问题一样。</p><p>之所以性能会降低，还有一个原因就是让本来有的索引失效。</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">select</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">order</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> order_code </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><blockquote><p>order_code 是 varchar 类型，假设我已经在 order_code 上建立了索引，如果是用“=”做查询条件的话，应该直接命中索引才对，查询速度会很快。但是，当查询条件后面的值类型不是 varchar，而是数值类型的话，MySQL 首先要对 order_code 字段做类型转换，转换为数值类型，这时候，之前建的索引也就不会命中，只能走全表扫描，查询性能指数级下降，搞不好，数据库直接查崩了。</p></blockquote><h1 id="sql-效率优化技巧" tabindex="-1">SQL 效率优化技巧 <a class="header-anchor" href="#sql-效率优化技巧" aria-label="Permalink to &quot;SQL 效率优化技巧&quot;">​</a></h1><p>所有的数据相关工作人员，包括数据开发、数据分析师、数据科学家等，多多少少会使用数据库，我们很多的业务数据也是存放在业务表中。但即使是同一个需求，不同人写出的 SQL 效率上也会有很大差别，而我们在数据岗位面试的时候，也会考察相关的技能和思考，在本篇文章中，将给大家梳理 SQL 中可以用于优化效率和提速的核心要求。</p><h2 id="使用正则regexp-like代替like" tabindex="-1">使用正则regexp_like代替LIKE <a class="header-anchor" href="#使用正则regexp-like代替like" aria-label="Permalink to &quot;使用正则regexp_like代替LIKE&quot;">​</a></h2><p>如下例所示，当我们要进行模糊匹配的时候（尤其是匹配项很多的时候），使用regexp_like代替LIKE可以提高效率。</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> phones</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#82AAFF;">lower</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%samsing&amp;</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">OR</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#82AAFF;">lower</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%apple&amp;</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">OR</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#82AAFF;">lower</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%htc&amp;</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">OR</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> phones</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span></span>
<span class="line"><span style="color:#A6ACCD;">    REGEXP_LIKE(</span><span style="color:#82AAFF;">lower</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">),</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">samsung|apple|htc</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><h2 id="使用regexp-extract代替-case-when-like" tabindex="-1">使用regexp_extract代替 Case-when Like <a class="header-anchor" href="#使用regexp-extract代替-case-when-like" aria-label="Permalink to &quot;使用regexp_extract代替 Case-when Like&quot;">​</a></h2><p>类似的，使用regexp_extract代替Case-when Like可以提高效率。</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">CASE</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">WHEN</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">concat</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%acer%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">then</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Acer</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">WHEN</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">concat</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%samsung%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">then</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Samsung</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#F78C6C;">WHEN</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">concat</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">LIKE</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">%dell%</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">then</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">Dell</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> brand</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> laptops</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span></span>
<span class="line"><span style="color:#A6ACCD;">      regexp_extract(</span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">,</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">(acer|samsung|dell)</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> brand</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> laptops</span></span></code></pre></div><h2 id="in子句转换为临时表" tabindex="-1">IN子句转换为临时表 <a class="header-anchor" href="#in子句转换为临时表" aria-label="Permalink to &quot;IN子句转换为临时表&quot;">​</a></h2><p>但我们进行数据选择时候，有时候会用到in作为条件选择，如果候选项非常多，利用临时表可能会带来更好的效率</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table1 </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> t1</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span></span>
<span class="line"><span style="color:#A6ACCD;">     itemid </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">3363134</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">5343</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">5555555</span><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> t1</span></span>
<span class="line"><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#F78C6C;">SELECT</span></span>
<span class="line"><span style="color:#A6ACCD;">           itemid</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">            </span><span style="color:#F78C6C;">SELECT</span></span>
<span class="line"><span style="color:#A6ACCD;">                 split(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">3363134, 5343, 5555555</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> bar</span></span>
<span class="line"><span style="color:#A6ACCD;">           )</span></span>
<span class="line"><span style="color:#A6ACCD;">           </span><span style="color:#F78C6C;">CROSS JOIN</span></span>
<span class="line"><span style="color:#A6ACCD;">                   UNNEST(bar) </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> t(itemid)</span></span>
<span class="line"><span style="color:#A6ACCD;">      ) </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> table2 </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> t2</span></span>
<span class="line"><span style="color:#F78C6C;">ON</span></span>
<span class="line"><span style="color:#A6ACCD;">  t1.itemid </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> t2.itemid</span></span></code></pre></div><h2 id="将-join-的表从大到小排序" tabindex="-1">将 JOIN 的表从大到小排序 <a class="header-anchor" href="#将-join-的表从大到小排序" aria-label="Permalink to &quot;将 JOIN 的表从大到小排序&quot;">​</a></h2><p>当我们要进行表关联（join）的时候，我们可以对表基于大小进行一个排序，把大表排在前面，小表排在后面，也会带来效率的提升。</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> small_table</span></span>
<span class="line"><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> large_table</span></span>
<span class="line"><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> small_table.id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> large_table.id</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> large_table</span></span>
<span class="line"><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> small_table</span></span>
<span class="line"><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> small_table.id </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> large_table.id</span></span></code></pre></div><h2 id="使用简单的表关联条件" tabindex="-1">使用简单的表关联条件 <a class="header-anchor" href="#使用简单的表关联条件" aria-label="Permalink to &quot;使用简单的表关联条件&quot;">​</a></h2><p>如果我们要基于条件对两个表进行连接，那条件中尽量不要出现复杂函数，如果一定需要使用，那我们可以先用函数对表的数据处理产出用于连接的字段。 如下例中，我们对a和b表进行连接，条件是b表的「年」「月」「日」拼接后和a表的日期一致，那粗糙的写法和优化的写法分别如下：</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table1 a</span></span>
<span class="line"><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> table2 b</span></span>
<span class="line"><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> a.date </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">CONCAT</span><span style="color:#A6ACCD;">(b.year, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">-</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, b.month, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">-</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, b.day)</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table1 a</span></span>
<span class="line"><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">name</span><span style="color:#A6ACCD;">, </span><span style="color:#82AAFF;">CONCAT</span><span style="color:#A6ACCD;">(b.year, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">-</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, b.month, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">-</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, b.day) </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">date</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table2 b</span></span>
<span class="line"><span style="color:#A6ACCD;">) new</span></span>
<span class="line"><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> a.date </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> new.date</span></span></code></pre></div><h2 id="分组的字段按照类别取值种类数排序" tabindex="-1">分组的字段按照类别取值种类数排序 <a class="header-anchor" href="#分组的字段按照类别取值种类数排序" aria-label="Permalink to &quot;分组的字段按照类别取值种类数排序&quot;">​</a></h2><p>如果我们需要对数据按照多个字段分组，尤其是字段中有id类这种取值非常多的类别字段，我们应当把它排在最前面，这也可以对效率有一些帮助。</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">  main_category,</span></span>
<span class="line"><span style="color:#A6ACCD;">  sub_category,</span></span>
<span class="line"><span style="color:#A6ACCD;">  itemid</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(price)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span></span>
<span class="line"><span style="color:#A6ACCD;">  table1</span></span>
<span class="line"><span style="color:#F78C6C;">GROUP BY</span></span>
<span class="line"><span style="color:#A6ACCD;">  main_category, sub_category, itemid</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span></span>
<span class="line"><span style="color:#A6ACCD;">  main_category,</span></span>
<span class="line"><span style="color:#A6ACCD;">  sub_category,</span></span>
<span class="line"><span style="color:#A6ACCD;">  itemid</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(price)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span></span>
<span class="line"><span style="color:#A6ACCD;">  table1</span></span>
<span class="line"><span style="color:#F78C6C;">GROUP BY</span></span>
<span class="line"><span style="color:#A6ACCD;">  itemid, sub_category, main_category</span></span></code></pre></div><h2 id="避免-where-子句中的子查询" tabindex="-1">避免 WHERE 子句中的子查询 <a class="header-anchor" href="#避免-where-子句中的子查询" aria-label="Permalink to &quot;避免 WHERE 子句中的子查询&quot;">​</a></h2><p>当我们要查询的语句的where条件中包含子查询时，我们可以通过with语句构建临时表来调整连接条件，提升效率，如下：</p><p>💦 错误代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">sum</span><span style="color:#A6ACCD;">(price)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table1</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> itemid </span><span style="color:#F78C6C;">in</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> itemid</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table2</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span></span></code></pre></div><p>💦 好代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">WITH</span><span style="color:#A6ACCD;"> t2</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> itemid</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;">   table2)</span></span>
<span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">Sum</span><span style="color:#A6ACCD;">(price)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;">   table1 </span><span style="color:#F78C6C;">AS</span><span style="color:#A6ACCD;"> t1</span></span>
<span class="line"><span style="color:#A6ACCD;">       </span><span style="color:#F78C6C;">JOIN</span><span style="color:#A6ACCD;"> t2</span></span>
<span class="line"><span style="color:#A6ACCD;">         </span><span style="color:#F78C6C;">ON</span><span style="color:#A6ACCD;"> t1.itemid </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> t2.itemid</span></span></code></pre></div><h2 id="取最大直接用max而非rank后取第1" tabindex="-1">取最大直接用Max而非Rank后取第1 <a class="header-anchor" href="#取最大直接用max而非rank后取第1" aria-label="Permalink to &quot;取最大直接用Max而非Rank后取第1&quot;">​</a></h2><p>这一条很好理解，如果我们要取某字段最大取值，我们直接使用 max，而不要用 rank 排序后取第 1，如下代码所示：</p><p>💦 低效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECt</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">*</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> userid, </span><span style="color:#82AAFF;">rank</span><span style="color:#A6ACCD;">() </span><span style="color:#F78C6C;">over</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">order by</span><span style="color:#A6ACCD;"> prdate </span><span style="color:#F78C6C;">desc</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">as</span><span style="color:#A6ACCD;"> rank</span></span>
<span class="line"><span style="color:#A6ACCD;">     </span><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span>
<span class="line"><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#F78C6C;">WHERE</span><span style="color:#A6ACCD;"> ranking </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span></code></pre></div><p>💦 高效代码</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">SELECT</span><span style="color:#A6ACCD;"> userid, </span><span style="color:#82AAFF;">max</span><span style="color:#A6ACCD;">(prdate)</span></span>
<span class="line"><span style="color:#F78C6C;">FROM</span><span style="color:#A6ACCD;"> table1</span></span>
<span class="line"><span style="color:#F78C6C;">GROUP BY</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1</span></span></code></pre></div><h2 id="其他优化点" tabindex="-1">其他优化点 <a class="header-anchor" href="#其他优化点" aria-label="Permalink to &quot;其他优化点&quot;">​</a></h2><ul><li>对于大表，利用approx_distinct()代替count(distinct)来计数。</li><li>对于大表，利用approx_percentie(metric,0.5)代替median。</li><li>尽可能避免使用UNION。</li></ul><h1 id="批量插入效率最高" tabindex="-1">批量插入效率最高 <a class="header-anchor" href="#批量插入效率最高" aria-label="Permalink to &quot;批量插入效率最高&quot;">​</a></h1><blockquote><p>我们在操作大型数据表或者日志文件的时候经常会需要写入数据到数据库，那么最合适的方案就是数据库的批量插入。只是我们在执行批量操作的时候，一次插入多少数据才合适呢？</p></blockquote><blockquote><p>假如需要插入的数据有百万条，那么一次批量插入多少条的时候，效率会高一些呢？这里博主和大家一起探讨下这个问题，应用环境为批量插入数据到临时表。</p></blockquote><h2 id="批量插入前准备" tabindex="-1">批量插入前准备 <a class="header-anchor" href="#批量插入前准备" aria-label="Permalink to &quot;批量插入前准备&quot;">​</a></h2><blockquote><p>博主本地原本是循环查出来的数据，然后每1000条插入一次，直至完成插入操作。但是为什么要设置1000条呢，实不相瞒，这是因为项目里的其他批量插入都是一次插1000条。。汗，博主不服，所以想要测试下。</p></blockquote><p>首先是查看当前数据库的版本，毕竟各个版本之间存在差异，脱离版本讲数据库就是耍流氓（以前没少耍啊）：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">mysql&gt; select version();</span></span>
<span class="line"><span style="color:#A6ACCD;">+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| version()  |</span></span>
<span class="line"><span style="color:#A6ACCD;">+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| 5.6.34-log |</span></span>
<span class="line"><span style="color:#A6ACCD;">+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">1 row in set (0.00 sec)</span></span></code></pre></div><h3 id="_1、插入到数据表的字段" tabindex="-1">1、插入到数据表的字段 <a class="header-anchor" href="#_1、插入到数据表的字段" aria-label="Permalink to &quot;1、插入到数据表的字段&quot;">​</a></h3><p>对于手动创建的临时表来说，字段当然是越少越好，而且字段占用的空间要尽量小一些，这样临时表不至于太大，影响表操作的性能。这里需要插入的字段是：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">字段1 int(10)</span></span>
<span class="line"><span style="color:#A6ACCD;">字段2 int(10)</span></span>
<span class="line"><span style="color:#A6ACCD;">字段3 int(10)</span></span>
<span class="line"><span style="color:#A6ACCD;">字段4 varchar(10)</span></span></code></pre></div><p>我们一共插入四个字段，分别是3个int类型的，一个varchar类型的，整体来说这些字段都比较小，占用的内存空间会小一些。</p><h3 id="_2、计算一行字段占用的空间" tabindex="-1">2、计算一行字段占用的空间 <a class="header-anchor" href="#_2、计算一行字段占用的空间" aria-label="Permalink to &quot;2、计算一行字段占用的空间&quot;">​</a></h3><p>对于innodb引擎来说，int类型可以存储4个字节，里面的<code>Int(M)</code>并不会影响存储字节的大小，这个M只是数据的展示位数，和mysql的ZEROFILL属性有关，即在数字长度不够的数据前面填充0，以达到设定的长度。此处不多说，想要了解的朋友可以百度一下，还是很有意思的。</p><p><code>varchar（10）</code>代表可以存储10个字符，不管是英文还是中文，最多都是10个，这部分假设存储的是中文，在utf-8mb4下，10个中文占用<code>10*4 = 40</code>个字节那么一行数据最多占用：<code>4+4+4+40 = 52</code>字节</p><h3 id="_3、插入操作整体时间分配⭐" tabindex="-1">3、插入操作整体时间分配⭐ <a class="header-anchor" href="#_3、插入操作整体时间分配⭐" aria-label="Permalink to &quot;3、插入操作整体时间分配⭐&quot;">​</a></h3><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">链接耗时 （30%）</span></span>
<span class="line"><span style="color:#A6ACCD;">发送query到服务器 （20%）</span></span>
<span class="line"><span style="color:#A6ACCD;">解析query （20%）</span></span>
<span class="line"><span style="color:#A6ACCD;">插入操作 （10% * 词条数目）</span></span>
<span class="line"><span style="color:#A6ACCD;">插入index （10% * Index的数目）</span></span>
<span class="line"><span style="color:#A6ACCD;">关闭链接 （10%）</span></span></code></pre></div><p>从这里可以看出来，真正耗时的不是操作，而是链接，解析的过程。单条sql的话，会在链接，解析部分耗费大量的时间，因此速度会很慢，所以我们一般都是采用批量插入的操作，争取在一次链接里面写入尽可能多的数据，以此来提升插入的速度。但是这个尽可能多的数据是多少呢？一次到底插入多少才合适呢？</p><h2 id="批量插入数据测试" tabindex="-1">批量插入数据测试 <a class="header-anchor" href="#批量插入数据测试" aria-label="Permalink to &quot;批量插入数据测试&quot;">​</a></h2><p>开始测试，但是一开始插入多少是合适的呢，是否有上限？查询mysql手册，我们知道sql语句是有大小限制的。</p><h3 id="_1、sql语句的大小限制" tabindex="-1">1、SQL语句的大小限制 <a class="header-anchor" href="#_1、sql语句的大小限制" aria-label="Permalink to &quot;1、SQL语句的大小限制&quot;">​</a></h3><p><code>my.ini</code> 里有 <code>max_allowed_packet</code> 这个参数控制通信的 packet 大小。mysql默认的sql语句的最大限制是1M（mysql5.7的客户端默认是16M，服务端默认是4M），可以根据设置查看。官方解释是适当增大 <code>max_allowed_packet</code> 参数可以使client端到server端传递大数据时，系统能够分配更多的扩展内存来处理。</p><blockquote><p>官方手册：<a href="https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html" target="_blank" rel="noreferrer">https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html</a></p></blockquote><h3 id="_2、查看服务器上的参数" tabindex="-1">2、查看服务器上的参数 <a class="header-anchor" href="#_2、查看服务器上的参数" aria-label="Permalink to &quot;2、查看服务器上的参数&quot;">​</a></h3><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">mysql&gt; show variables like &#39;%max_allowed_packet%&#39;;</span></span>
<span class="line"><span style="color:#A6ACCD;">+--------------------------+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| Variable_name            | Value      |</span></span>
<span class="line"><span style="color:#A6ACCD;">+--------------------------+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| max_allowed_packet       | 33554432   |</span></span>
<span class="line"><span style="color:#A6ACCD;">| slave_max_allowed_packet | 1073741824 |</span></span>
<span class="line"><span style="color:#A6ACCD;">+--------------------------+------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">2 rows in set (0.00 sec)</span></span></code></pre></div><p><code>33554432字节 = 32M</code> ，也就是规定大小不能超过32M。</p><h3 id="_3、计算一次能插入的最大行记录" tabindex="-1">3、计算一次能插入的最大行记录 <a class="header-anchor" href="#_3、计算一次能插入的最大行记录" aria-label="Permalink to &quot;3、计算一次能插入的最大行记录&quot;">​</a></h3><p>1M计算的话，<code>(1024*1024)/52 ≈ 20165</code> ，为了防止溢出，最大可一次性插入20000条（根据自己的配置和sql语句大小计算）。那么32M的话就是：<code>20000 *32 = 640000</code> 也就是64W条。</p><h3 id="_4、测试插入数据比对" tabindex="-1">4、测试插入数据比对 <a class="header-anchor" href="#_4、测试插入数据比对" aria-label="Permalink to &quot;4、测试插入数据比对&quot;">​</a></h3><p>插入11W条数据，按照每次<code>10,600,1000,20000,80000</code>来测试：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| count(c1.uin) |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">|         110000 |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+</span></span></code></pre></div><p>有个博客说一次插入10条最快，，我觉得一次插的有点少，咱们试试</p><blockquote><p>参考：<a href="https://www.cnblogs.com/aicro/p/3851434.html" target="_blank" rel="noreferrer">https://www.cnblogs.com/aicro/p/3851434.html</a></p></blockquote><p>这个博主测试后，认为一次插10条是性能最快的，他的每条记录是3kb，相当于我的59行数据，取个整数60，那么对于这个博主是插入10条，对我来说插入：600，这几个值都试试。</p><p>耗时：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">11W的数据，每次插入10条。耗时：2.361s</span></span>
<span class="line"><span style="color:#A6ACCD;">11W的数据，每次插入600条。耗时：0.523s</span></span>
<span class="line"><span style="color:#A6ACCD;">11W的数据，每次插入1000条。耗时：0.429s</span></span>
<span class="line"><span style="color:#A6ACCD;">11W的数据，每次插入20000条。耗时：0.426s</span></span>
<span class="line"><span style="color:#A6ACCD;">11W的数据，每次插入80000条。耗时：0.352s</span></span></code></pre></div><p>从这部分看，随着批量插入的增加，速度略有提升，最起码一次插10条应该不是最佳的。插入数据量多，减少了循环的次数，也就是在数据库链接部分的耗时有所减少，只是这个8W并不是极限数据，具体一次插入多少条，还有待参考。</p><p>加大数据量到24w</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| count(c1.uin) |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">|        241397 |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+</span></span></code></pre></div><p>耗时：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">24W的数据，每次插入10条。耗时：4.445s</span></span>
<span class="line"><span style="color:#A6ACCD;">24W的数据，每次插入600条。耗时：1.187s</span></span>
<span class="line"><span style="color:#A6ACCD;">24W的数据，每次插入1000条。耗时：1.13s</span></span>
<span class="line"><span style="color:#A6ACCD;">24W的数据，每次插入20000条。耗时：0.933s</span></span>
<span class="line"><span style="color:#A6ACCD;">24W的数据，每次插入80000条。耗时：0.753s</span></span></code></pre></div><p>一次插入24W反而性能最佳，这么代表我们的测试数据量依然不够。</p><p>加大测试量到42W</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| count(c1.uin) |</span></span>
<span class="line"><span style="color:#A6ACCD;">+---------------+</span></span>
<span class="line"><span style="color:#A6ACCD;">|        418859 |</span></span></code></pre></div><p>耗时：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入1000条。耗时：2.216s</span></span>
<span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入80000条。耗时：1.777s</span></span>
<span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入16W条。耗时：1.523s</span></span>
<span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入20W条。耗时：1.432s</span></span>
<span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入30W条。耗时：1.362s</span></span>
<span class="line"><span style="color:#A6ACCD;">42W的数据，每次插入40W条。耗时：1.764s</span></span></code></pre></div><p>随着插入量的增加，批量插入条数多了之后，性能是有所提升的。但是在达到30W以上之后，效率反而有所下降。这部分我的理解是mysql是要分配一定的内存给传过来的数据包使用，当批量插入的数据量到达一定程度之后，一次插入操作的开销就很耗费内存了。</p><blockquote><p>个人感觉，最佳大小是<code>max_allowed_packet</code>的一半，也就是极限能插入64W，选用32W也许性能会更好一些，同时也不会对mysql的其他操作产生太大的影响。</p></blockquote><h3 id="_5、达到限制的最大-性能真的好吗" tabindex="-1">5、达到限制的最大，性能真的好吗？ <a class="header-anchor" href="#_5、达到限制的最大-性能真的好吗" aria-label="Permalink to &quot;5、达到限制的最大，性能真的好吗？&quot;">​</a></h3><p>博主疯狂谷歌百度，都没有找到有人来具体的说一下这个问题，不过在高性能mysql里面发现一句话：</p><blockquote><p>客户端用一个单独的数据包将查询请求发送给服务器，所以当查询语句很长的时候，需要设置<code>max_allowed_packet</code>参数。但是需要注意的是，如果查询实在是太大，服务端会拒绝接收更多数据并抛出异常。与之相反的是，服务器响应给用户的数据通常会很多，由多个数据包组成。但是当服务器响应客户端请求时，客户端必须完整的接收整个返回结果，而不能简单的只取前面几条结果，然后让服务器停止发送。因而在实际开发中，尽量保持查询简单且只返回必需的数据，减小通信间数据包的大小和数量是一个非常好的习惯，这也是查询中尽量避免使用<code>SELECT *</code>以及加上<code>LIMIT</code>限制的原因之一。</p></blockquote><p>后面通过各种百度，博主觉得最大只是代表传输数据包的最大长度，但性能是不是最佳就要从各个方面来分析了。比如下面列出的插入缓冲，以及插入索引时对于缓冲区的剩余空间需求，以及事务占有的内存等，都会影响批量插入的性能。</p><h2 id="其他影响插入性能的因素" tabindex="-1">其他影响插入性能的因素 <a class="header-anchor" href="#其他影响插入性能的因素" aria-label="Permalink to &quot;其他影响插入性能的因素&quot;">​</a></h2><h3 id="_1、意缓冲区的大小使用情况" tabindex="-1">1、意缓冲区的大小使用情况 <a class="header-anchor" href="#_1、意缓冲区的大小使用情况" aria-label="Permalink to &quot;1、意缓冲区的大小使用情况&quot;">​</a></h3><p>在分析源码的过程中，有一句话：如果<code>buffer pool</code>余量不足25%，插入失败，返回<code>DB_LOCK_TABLE_FULL</code>。这个错误并不是直接报错：<code>max_allowed_packet</code> 不够大之类的，这个错误是因为对于innodb引擎来说，一次插入是涉及到事务和锁的，在插入索引的时候，要判断缓冲区的剩余情况，所以插入并不能仅仅只考虑<code>max_allowed_packet</code>的问题，也要考虑到缓冲区的大小。</p><blockquote><p>参考淘宝的数据库日报：<a href="http://mysql.taobao.org/monthly/2017/09/10/" target="_blank" rel="noreferrer">http://mysql.taobao.org/monthly/2017/09/10/</a></p></blockquote><h3 id="_2、插入缓存" tabindex="-1">2、插入缓存 <a class="header-anchor" href="#_2、插入缓存" aria-label="Permalink to &quot;2、插入缓存&quot;">​</a></h3><p>另外对于innodb引擎来说，因为存在插入缓存（Insert Buffer）这个概念，所以在插入的时候也是要耗费一定的缓冲池内存的。当写密集的情况下，插入缓冲会占用过多的缓冲池内存，默认最大可以占用到1/2的缓冲池内存，当插入缓冲占用太多缓冲池内存的情况下，会影响到其他的操作。</p><p>也就是说，插入缓冲受到缓冲池大小的影响，缓冲池大小为：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">mysql&gt; show variables like &#39;innodb_buffer_pool_size&#39;;</span></span>
<span class="line"><span style="color:#A6ACCD;">+-------------------------+-----------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| Variable_name           | Value     |</span></span>
<span class="line"><span style="color:#A6ACCD;">+-------------------------+-----------+</span></span>
<span class="line"><span style="color:#A6ACCD;">| innodb_buffer_pool_size | 134217728 |</span></span>
<span class="line"><span style="color:#A6ACCD;">+-------------------------+-----------+</span></span></code></pre></div><p>换算后的结果为：128M，也就是说，插入缓存最多可以占用64M的缓冲区大小。这个大小要超过咱们设置的sql语句大小，所以可以忽略不计。</p><p>详细解释：</p><blockquote><p>我们都知道，在InnoDB引擎上进行插入操作时，一般需要按照主键顺序进行插入，这样才能获得较高的插入性能。当一张表中存在非聚簇的且不唯一的索引时，在插入时，数据页的存放还是按照主键进行顺序存放，但是对于非聚簇索引叶节点的插入不再是顺序的了，这时就需要离散的访问非聚簇索引页，由于随机读取的存在导致插入操作性能下降。</p></blockquote><p>InnoDB为此设计了Insert Buffer来进行插入优化。对于非聚簇索引的插入或者更新操作，不是每一次都直接插入到索引页中，而是先判断插入的非聚集索引是否在缓冲池中，若在，则直接插入；若不在，则先放入到一个<code>Insert Buffer</code>中。</p><p>看似数据库这个非聚集的索引已经查到叶节点，而实际没有，这时存放在另外一个位置。然后再以一定的频率和情况进行<code>Insert Buffer</code>和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中，这样就大大提高了对于非聚簇索引的插入性能。</p><blockquote><p>参考：<a href="https://cloud.tencent.com/developer/article/1200824" target="_blank" rel="noreferrer">https://cloud.tencent.com/developer/article/1200824</a></p><p>参考：mysql技术内幕 Innodb篇</p></blockquote><h3 id="_3、使用事务提升效率" tabindex="-1">3、使用事务提升效率 <a class="header-anchor" href="#_3、使用事务提升效率" aria-label="Permalink to &quot;3、使用事务提升效率&quot;">​</a></h3><p>还有一种说法，使用事务可以提高数据的插入效率，这是因为进行一个<code>INSERT</code>操作时，MySQL内部会建立一个事务，在事务内才进行真正插入处理操作。通过使用事务可以减少创建事务的消耗，所有插入都在执行后才进行提交操作。大概如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">START TRANSACTION;</span></span>
<span class="line"><span style="color:#A6ACCD;">INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) </span></span>
<span class="line"><span style="color:#A6ACCD;">    VALUES (&#39;0&#39;, &#39;userid_0&#39;, &#39;content_0&#39;, 0);</span></span>
<span class="line"><span style="color:#A6ACCD;">INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) </span></span>
<span class="line"><span style="color:#A6ACCD;">    VALUES (&#39;1&#39;, &#39;userid_1&#39;, &#39;content_1&#39;, 1);</span></span>
<span class="line"><span style="color:#A6ACCD;">...</span></span>
<span class="line"><span style="color:#A6ACCD;">COMMIT;</span></span></code></pre></div><blockquote><p>参考：<a href="https://my.oschina.net/songhongxu/blog/163063" target="_blank" rel="noreferrer">https://my.oschina.net/songhongxu/blog/163063</a></p></blockquote><p>事务需要控制大小，事务太大可能会影响执行的效率。MySQL有<code>innodb_log_buffer_size</code>配置项，超过这个值会把innodb的数据刷到磁盘中，这时，效率会有所下降。所以比较好的做法是，在数据达到这个这个值前进行事务提交。</p><p>查看：<code>show variables like &#39;%innodb_log_buffer_size%&#39;;</code></p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">+------------------------+----------+</span></span>
<span class="line"><span style="color:#A6ACCD;">        | Variable_name          | Value    |</span></span>
<span class="line"><span style="color:#A6ACCD;">        +------------------------+----------+</span></span>
<span class="line"><span style="color:#A6ACCD;">        | innodb_log_buffer_size | 67108864 |</span></span>
<span class="line"><span style="color:#A6ACCD;">        +------------------------+----------+</span></span></code></pre></div><p>大概是：64M</p><p>这种写法和批量写入的效果差不多，只不过sql语句还是单句的，然后统一提交。一个瓶颈是SQL语句的大小，一个瓶颈是事务的大小。当我们在提交sql的时候，首先是受到sql大小的限制，其次是受到事务大小的限制。在开启事务的情况下使用批量插入，会节省不少事务的开销，如果要追求极致的速度的话，建议是开着事务插入的。</p><blockquote><p>不过需要注意一下，内存是有限且共享的，如果批量插入占用太多的事务内存，那么势必会对其他的业务操作等有一定的影响。</p></blockquote><h3 id="_4、通过配置提升读写性能" tabindex="-1">4、通过配置提升读写性能 <a class="header-anchor" href="#_4、通过配置提升读写性能" aria-label="Permalink to &quot;4、通过配置提升读写性能&quot;">​</a></h3><p>也可以通过增大<code>innodb_buffer_pool_size</code> 缓冲区来提升读写性能，只是缓冲区是要占用内存空间的，内存很珍贵，所以这个方案在内存富裕，而性能瓶颈的时候，可以考虑下。</p><blockquote><p>参考：<a href="https://my.oschina.net/anuodog/blog/3002941" target="_blank" rel="noreferrer">https://my.oschina.net/anuodog/blog/3002941</a></p></blockquote><h3 id="_5、索引影响插入性能" tabindex="-1">5、索引影响插入性能 <a class="header-anchor" href="#_5、索引影响插入性能" aria-label="Permalink to &quot;5、索引影响插入性能&quot;">​</a></h3><p>如果表中存在多个字段索引，当对表中的数据进行增加、删除和修改的时候，索引也要动态的维护。这样就降低了数据的插入速度。对于普通的数据表，主键索引是肯定要有的，想要加快性能的话，就是要有序插入，每次插入记录都在索引的最后面，索引的定位效率很高，并且对索引调整较小。如果插入的记录在索引中间，需要B+tree进行分裂合并等处理，会消耗比较多计算资源，并且插入记录的索引定位效率会下降，数据量较大时会有频繁的磁盘操作。</p><h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>博主经过测试+谷歌，最终是选用的一次批量插入数据量为<code>max_allowed_packet</code>大小的一半。只是在不断的搜索中，发现影响插入性能的地方挺多的，如果仅仅是拿<code>max_allowed_packet</code>这个参数作为分析，其实是没有意义的，这个参数只是设置最大值，但并不是最佳性能。</p><blockquote><p>不过需要注意，由于sql语句比较大，所以才执行完插入操作之后，一定要释放变量，不要造成无谓的内存损耗，影响程序性能。</p></blockquote><blockquote><p>对于我们的mysql来说也是一样的，mysql的最佳性能是建立在各个参数的合理设置上，这样协同干活儿的效果最佳。如果其他设置不到位的话，就像是木桶原理一样，哪怕内存缓冲区设置的很大，但是性能取决的反而是设置最差的那个配置。关于mysql的配置调优，我们都在路上，加油！</p></blockquote><h1 id="系统上线前sql脚本的9大坑" tabindex="-1">系统上线前SQL脚本的9大坑 <a class="header-anchor" href="#系统上线前sql脚本的9大坑" aria-label="Permalink to &quot;系统上线前SQL脚本的9大坑&quot;">​</a></h1><blockquote><p>系统上线时，非常容易出问题。即使之前在测试环境，已经执行过SQL脚本了。但是有时候，在系统上线时，在生产环境执行相同的SQL脚本，还是有可能出现一些问题。</p></blockquote><blockquote><p>有些小公司，SQL脚本是开发自己执行的，有很大的风险。有些大厂，有专业的DBA把关，但DBA也不是万能的，还是有可能会让一些错误的SQL脚本被生产环境执行了，比如：update语句的顺序不对。</p></blockquote><p>今天跟大家一起聊聊，系统上线时SQL脚本的9大坑，以便于大家吸取教训，能够防微杜渐，希望对你会有所帮助。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211626830.png" alt="image-20230221162609748" style="zoom:67%;"><h2 id="_1-漏脚本了" tabindex="-1">1 漏脚本了 <a class="header-anchor" href="#_1-漏脚本了" aria-label="Permalink to &quot;1 漏脚本了&quot;">​</a></h2><p>我们上线时执行的SQL脚本，出现次数最多的问题，应该是<code>漏脚本</code>了。</p><ul><li>有时候少加了一个字段。</li><li>有时候字段的注释没有及时修改。</li><li>有时候有些新表没创建。</li><li>有时候字段类型忘了修改。</li></ul><p>等等。</p><p>我们的SQL脚本中漏脚本的情况有很多。</p><p>那么，如何解决这个问题呢？</p><p>答：将SQL脚本做成代码的一部分。在项目的代码中，创建一个专门的<code>sql目录</code>，在该目录下根据每个迭代创建一个子目录，比如：mv3.2.1，将SQL脚本存放到mv3.2.1下。</p><p>我们在开发环境<code>任何</code>对表的相关操作，比如：增加字段、修改字段类型、修改注释、增加索引、创建表等等，都需要通过SQL语句操作，然后把该SQL语句，整理到SQL脚本中。</p><p>最后提交到公司的<code>GitLab</code>上，我们在测试环境和生产环境发版时，去GitLab上找相关迭代版本的SQL脚本执行。</p><p>通过该方式基本可以解决<code>漏脚本</code>的问题。</p><h2 id="_2-脚本语法错误" tabindex="-1">2 脚本语法错误 <a class="header-anchor" href="#_2-脚本语法错误" aria-label="Permalink to &quot;2 脚本语法错误&quot;">​</a></h2><p>有些小伙伴看到这个标题可能有点懵，SQL脚本不是已经在测试环境执行过了吗？为什么还会出现语法错误？</p><p>比如说有这样的场景：原本你的SQL脚本没问题的，但没有按照规范，给一张表的添加多个字段，你写了多条<code>ALTER</code>语句。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> t_user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> column  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">work</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">30</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">工作</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> t_user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> column  </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">provice</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">  COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">籍贯</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>在上线时，你给DBA提SQL工单时，该工单被DBA审核拒绝打回来了。</p><p>然后为了赶时间，你急急忙忙把多条<code>ALTER</code>语句改成一条<code>ALTER</code>语句。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> t_user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">work</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">30</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">工作</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">,</span></span>
<span class="line"><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">provice</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">10</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">  COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">籍贯</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>但在修改的过程中，有地方少了一个<code>逗号</code>，就可能会出现SQL语法错误。</p><p>因此，不管是什么SQL语句，要养成好习惯，只要修改了一定要记得到开发环境的数据库中，先执行测试一下，切勿直接提到生产环境，即使你有很大的把握，也需要再更慎重一些。</p><p>这样基本可以避免SQL语法错误的问题。</p><h2 id="_3-脚本顺序不对" tabindex="-1">3 脚本顺序不对 <a class="header-anchor" href="#_3-脚本顺序不对" aria-label="Permalink to &quot;3 脚本顺序不对&quot;">​</a></h2><p>有些时候，我们在上线系统时，DBA在执行SQL脚本的时候，没有报错，但最后的数据就是不对。</p><p>有可能是<code>脚本顺序不对</code>导致的。</p><p>比如有这样一种场景：你往某张表通过insert初始化了一条数据。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">sue</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">t_user</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">code</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">height</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">address</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">work</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">provice</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">101</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">21</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">周星驰</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">173</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">香港</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>另外一个人要基于你这条数据，通过update修改数据。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> t_user </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">25</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>你们提了两条SQL脚本。</p><p>另外一个人先提的，你后提的。</p><p>DBA先把他的SQL工单审核通过了，先update数据，此时通过id是没法找到那条数据的，影响行数为0。</p><p>然后DBA再审核你的SQL工单，审核通过了，插入了一条数据。</p><p>由于SQL脚本的顺序不对，导致最终系统上线时的数据不对。</p><p>那么这个问题要如何解决呢？</p><p>双方要事先沟通好，把另外一个同事的SQL脚本加到你的初始化脚本中，你的脚本在初始化时，直接去修改数据即可。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">INSERT INTO</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">sue</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">.</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">t_user</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">id</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">code</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">age</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">name</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">height</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">address</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">work</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">provice</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">VALUES</span><span style="color:#A6ACCD;"> (</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">101</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">25</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">周星驰</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">173</span><span style="color:#A6ACCD;">, </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">香港</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">, </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;">);</span></span></code></pre></div><p>这样可以避免执行顺序问题。</p><h2 id="_4-执行时机不对" tabindex="-1">4 执行时机不对 <a class="header-anchor" href="#_4-执行时机不对" aria-label="Permalink to &quot;4 执行时机不对&quot;">​</a></h2><p>有些系统功能已经上线了，在后面的迭代中，为了尽量避免少影响线上功能，可以增加一个<code>pre</code>（即预生产环境）。</p><p>该环境跟生产环境是差不多的，连接了相同的数据库，使用了相同的apollo配置。</p><p>但唯一的区别是pre环境没有实际的用户流量，只能公司内部人员才能访问。</p><p>一般在迭代版本上线之前，先要把系统功能发布到pre环境中，测试通过之后，才能发布到<code>prod</code>（即生产环境）。</p><p>但有些SQL脚本，却没法再pre环境中执行，不然会影响生产环境。</p><p>比如：修改了字段类型，int改成varchar了，或者初始化数据时，初始化了一条新加的枚举数据。</p><p>由于pre环境是运行的最新代码，但prod环境还是运行的老代码。</p><p>如果在发布pre环境时，直接执行SQL脚本，可能会导致prod环境的功能异常。</p><p>因此要搞清楚SQL脚本的执行时机，哪些是要在pre环境执行的，哪些是要在prod环境执行的。</p><p>我们在提SQL工单时，千万不要一股脑就提了，一定要区分时机。</p><p>在发pre环境时，要么不提发prod环境的SQL脚本。要么，在工单的名称上做区分，比如增加<code>prod_</code>开头的标识。</p><p>这样可以解决<code>SQL脚本执行时机</code>的问题。</p><h2 id="_5-搞错数据库了" tabindex="-1">5 搞错数据库了 <a class="header-anchor" href="#_5-搞错数据库了" aria-label="Permalink to &quot;5 搞错数据库了&quot;">​</a></h2><p>有时候，我们的数据库做了<code>分库分表</code>，或者增加<code>备份库</code>。</p><p>在执行SQL脚本的时候，由于我们自己的疏忽，提SQL工单时选错数据库了，或者DBA的疏忽，在执行SQL工单时搞错数据库了，就会出现问题。</p><p>建议我们的SQL脚本增加库名，比如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">alter</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">table</span><span style="color:#A6ACCD;"> sue.t_user </span><span style="color:#F78C6C;">add</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">`</span><span style="color:#C3E88D;">work</span><span style="color:#89DDFF;">`</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">varchar</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">30</span><span style="color:#A6ACCD;">) </span><span style="color:#C792EA;">DEFAULT</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">NULL</span><span style="color:#A6ACCD;"> COMMENT </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">工作</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>这里增加库名：sue。</p><p>这样基本可以避免<code>选错数据库</code>的问题。</p><h2 id="_6-脚本耗时太长" tabindex="-1">6 脚本耗时太长 <a class="header-anchor" href="#_6-脚本耗时太长" aria-label="Permalink to &quot;6 脚本耗时太长&quot;">​</a></h2><p>有时候，我们的SQL脚本需要批量修改生产环境的一些数据，正常情况下一条update语句就能搞定。</p><p>例如：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> user </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">status</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>但由于user表的数据量非常大，我们在执行该SQL脚本之前，没有预先评估该SQL脚本的耗时情况，而选择直接在生产环境的数据库中执行。</p><p>假如该SQL脚本耗时非常长，比如要10分钟才能执行完，可能会导致user表长期锁表，影响正常的业务功能。</p><p>在该SQL脚本执行的过程中，极有可能会出现业务功能操作，导致的死锁问题。</p><p>因此，建议这种大批量的数据更新操作，要在用户较少的凌晨，分批多次执行。</p><p>我们要尽可能少的影响线上用户的功能。</p><p>此外，在生产环境增加字段，增加索引等操作，也能会导致长期锁表。也要避免在用户访问高峰期执行相关的SQL脚本。</p><h2 id="_7-脚本无法回滚" tabindex="-1">7 脚本无法回滚 <a class="header-anchor" href="#_7-脚本无法回滚" aria-label="Permalink to &quot;7 脚本无法回滚&quot;">​</a></h2><p>绝大多数系统上线是能够成功的，虽然过程中会遇到很多问题，但如果能够及时解决，也能够<code>上线成功</code>。</p><p>但如果有些问题，没法再规定的时间内解决，很有可能会导致<code>上线失败</code>。</p><p>如果上线失败，意味着代码和数据库的SQL脚本要回滚。</p><p>如果只回滚了代码，不回滚数据库，可能会导致很多系统异常。</p><p>因此，我们在准备SQL语句时，要留点心眼，顺便想想该SQL语句能否回滚。</p><p>对于update语句可以加上修改时间：</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#F78C6C;">update</span><span style="color:#A6ACCD;"> t_user </span><span style="color:#F78C6C;">set</span><span style="color:#A6ACCD;"> age</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">25</span><span style="color:#A6ACCD;">,</span><span style="color:#C792EA;">time</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">now</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">3</span><span style="color:#A6ACCD;">) </span><span style="color:#F78C6C;">where</span><span style="color:#A6ACCD;"> id</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span></code></pre></div><p>这样可以通过该时间追溯一次SQL操作修改的数据，方便后面做回滚。</p><p>有些时候我们要update的数据，是要通过多条sql语句查询出来的，比如：需要使用的id。</p><p>为了方便回滚我们可以增加<code>临时表</code>，保存这些id，后面就能追溯了。</p><p>当然有些开源的数据库管理平台，比如：Archery，是有自带SQL审核和回滚的功能。</p><h2 id="_8-忘了加索引" tabindex="-1">8 忘了加索引 <a class="header-anchor" href="#_8-忘了加索引" aria-label="Permalink to &quot;8 忘了加索引&quot;">​</a></h2><p>我们在增加了字段之后，非常容易忽略的一件事是：<code>加索引</code>。</p><p>特别是当前表数据量很大，而且增加的字段是另外一张表的id时，这种情况强烈建议增加索引。</p><p>如果我们上线系统时，在SQL脚本中，忘了给该字段增加索引。如果该id字段被大批量访问，全部走的全表扫描，可能会导致数据库性能直线下降，出现大量的<code>超时问题</code>。</p><p>所以建议我们在开发的时候，如果要增加字段的话，要养成良好习惯，想一想这个字段需不需要建<code>索引</code>。</p><p>如果不确定数据量的话，可以先到生产环境查询一下真实的用户数据，不然后续可能会引起比较大的<code>生产事故</code>。</p><h2 id="_9-字段改名" tabindex="-1">9 字段改名 <a class="header-anchor" href="#_9-字段改名" aria-label="Permalink to &quot;9 字段改名&quot;">​</a></h2><p>对于生产环境的表字段，通常情况下，我们不允许修改名称。</p><p>如果你在发布pre环境时，通过SQL脚本把某张表的某个字段名称修改了，pre环境代码使用了新的名称，系统没有问题。</p><p>但prod环境还是使用老的名称，所有使用该名称的sql语句，在代码执行过程中都会报错。</p><p>因此，禁止在生产环境通过SQL脚本<code>修改字段名称</code>。</p><p>当然系统上线时除了SQL脚本的这些坑之外，还有系统发版失败，代码合错分支，mq消息被pre消费了，无法回滚等等，还有很多问题。</p></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-37656e44><!--[--><!--]--><!----><nav class="prev-next" data-v-37656e44><div class="pager" data-v-37656e44><a class="pager-link prev" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-37656e44><span class="desc" data-v-37656e44>Previous page</span><span class="title" data-v-37656e44>MySQL进阶</span></a></div><div class="pager" data-v-37656e44><a class="pager-link next" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-37656e44><span class="desc" data-v-37656e44>Next page</span><span class="title" data-v-37656e44>MySQL设计</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"2、数据库_mysql_mysql面试_基础.md\":\"40da680a\",\"1、学前端_5、小程序_小程序项目.md\":\"60a1629b\",\"1、学前端_4、node_知识篇.md\":\"a7fb500e\",\"1、学前端_2、js_ts_es6 进阶.md\":\"6d07ba10\",\"1、学前端_3、vue_vue3_vue3进阶.md\":\"7ac622b4\",\"5、运维_jenkins.md\":\"929081f8\",\"1、学前端_2、js_ts_typescript.md\":\"875a4aa4\",\"2、数据库_mysql_mysql核心_设计.md\":\"7faf46d1\",\"2、数据库_mysql_mysql核心_基础.md\":\"d8e97f3e\",\"1、学前端_1、html_css_html基础.md\":\"7584d076\",\"1、学前端_5、专题篇_问题篇.md\":\"e893aaa2\",\"2、数据库_mysql_mysql面试_进阶.md\":\"f934806d\",\"3、springboot_运维_原理.md\":\"f4a39db6\",\"2、数据库_influxdb.md\":\"6e1711e1\",\"3、springboot_新特性.md\":\"cdf3e307\",\"mybatis_mybatisplus_jpa.md\":\"8e41681b\",\"1、学前端_5、小程序_小程序优化.md\":\"a2185198\",\"2、数据库_redis_redis基础.md\":\"856df0e0\",\"linux_实用脚本.md\":\"f2299dd5\",\"4、微服务_必备_分布式基础.md\":\"d49863d5\",\"2、数据库_redis_redis优化.md\":\"e66ae32f\",\"4、微服务_springsecurity_进阶篇.md\":\"235a8e9e\",\"5、运维_chatgpt.md\":\"10db3823\",\"2、数据库_mysql_分库分表.md\":\"e1c8a095\",\"start.md\":\"9bc1ff8d\",\"5、运维_github.md\":\"2ec6c735\",\"java学前端_css.md\":\"f11b47f0\",\"1、学前端_5、专题篇_知识篇.md\":\"a463ed8d\",\"linux_软件部署.md\":\"d6722925\",\"2、数据库_neo4j.md\":\"97ad22ac\",\"team.md\":\"ce467a6a\",\"nginx_实战篇.md\":\"7785486e\",\"index.md\":\"8c3ec167\",\"计算机基础_计算机网络_网络基础.md\":\"7a54a85d\",\"1、学前端_4、node_进阶篇.md\":\"60f6db69\",\"java_java集合.md\":\"a049b313\",\"1、学前端_3、vue_vue3_vue3高级.md\":\"614d1516\",\"1、学前端_5、小程序_微信小程序.md\":\"9a4be771\",\"5、运维_netty.md\":\"12ca0278\",\"2、数据库_mysql_mysql核心_运维.md\":\"83f97c16\",\"idea_vs code.md\":\"afdcb593\",\"java学前端_vue3_组件.md\":\"1086884e\",\"idea_chrome.md\":\"4a32afbc\",\"云原生_k8s.md\":\"db58e65a\",\"2、数据库_mysql_mysql核心_进阶.md\":\"61d16dff\",\"ssm_springbatch.md\":\"f799ab4a\",\"三高_分布式.md\":\"db1b8a1b\",\"2、数据库_elasticsearch_1、es基础.md\":\"04d17448\",\"linux_linux基础.md\":\"4b0bf394\",\"idea_idea插件.md\":\"fa86e45a\",\"可视化 _ 监控_可视化大屏.md\":\"004553bd\",\"2、数据库_mongodb_整合.md\":\"3c47d7f4\",\"4、微服务_springsecurity_基础篇.md\":\"534a3401\",\"4、微服务_进阶.md\":\"69095c58\",\"计算机基础_计算机基础_操作系统.md\":\"0f75d113\",\"可视化 _ 监控_zabbix.md\":\"71f2270e\",\"nginx_基础篇.md\":\"c7d8bb50\",\"1、学前端_4、node_项目实战.md\":\"bc5065b8\",\"2、数据库_redis_redis原理.md\":\"5cedf685\",\"可视化 _ 监控_监控基础.md\":\"ac56ce4d\",\"三高_高并发.md\":\"ea9ffc99\",\"2、数据库_redis_redis高级.md\":\"1d5872f6\",\"1、学前端_4、node_基础篇.md\":\"581cc13a\",\"2、数据库_mongodb_基础.md\":\"fb7a0a29\",\"idea_idea基础.md\":\"6f2f9638\",\"4、微服务_必备_sentinel.md\":\"2edfbf6c\",\"2、数据库_elasticsearch_3、es高级.md\":\"ef146606\",\"1、学前端_3、vue_vue3_vue3新语法.md\":\"8afd5409\",\"消息中间件_canal.md\":\"3949163c\",\"ssm_maven.md\":\"2c5e12ed\",\"4、微服务_springsecurity_高级篇.md\":\"882d3ff3\",\"linux_linux进阶.md\":\"188ef7b4\",\"计算机基础_设计模式_uml.md\":\"634ba256\",\"计算机基础_算法_leetcode.md\":\"77162fb9\",\"项目实战_小兔鲜_进阶篇1.md\":\"17c52c81\",\"1、学前端_2、js_ts_es6 基础.md\":\"fda3f18b\",\"项目实战_小兔鲜_进阶篇2.md\":\"a0f23006\",\"软件测试_测试基础.md\":\"8c1060cd\",\"2、数据库_redis_本地缓存.md\":\"00617fe6\",\"nginx_面试篇.md\":\"e3fb373a\",\"mybatis_mybatisplus_mybatis.md\":\"9239e0ad\",\"linux_shell.md\":\"ae53d83b\",\"2、数据库_mysql_mysql核心_优化.md\":\"36230425\",\"项目实战_项目推荐.md\":\"f9d97630\",\"mybatis_mybatisplus_mybatisplus.md\":\"0030fd35\",\"项目实战_百度地图_进阶篇.md\":\"c8b93267\",\"三高_高可用.md\":\"323840c5\",\"java_java新特性.md\":\"22abf56d\",\"软件测试_压力测试.md\":\"9ab44440\",\"java学前端_html_js.md\":\"e0fcd240\",\"2、数据库_redis_redis实战.md\":\"d6daeeab\",\"nginx_进阶篇.md\":\"e6b63195\",\"三高_秒杀.md\":\"3878bb64\",\"5、运维_git.md\":\"0264925c\",\"java_java进阶.md\":\"e79cb5b4\",\"并发 _ 多线程_基础篇.md\":\"7adbfac5\",\"项目实战_百度地图_基础篇.md\":\"8afa5954\",\"java学前端_react.md\":\"3ec827dd\",\"1、学前端_1、html_css_css基础.md\":\"01b56712\",\"项目实战_小兔鲜_基础篇.md\":\"646f5df5\",\"1、学前端_2、js_ts_js 基础.md\":\"cb13e36f\",\"可视化 _ 监控_监控进阶.md\":\"0cdbc292\",\"计算机基础_设计模式_基础篇.md\":\"51617287\",\"计算机基础_数据结构_基础篇.md\":\"b2bfd8d4\",\"项目实战_苍穹外卖_进阶篇.md\":\"48415e41\",\"ssm_spring.md\":\"ab514659\",\"消息中间件_rabbitmq.md\":\"45b1eb28\",\"1、学前端_1、html_css_网页进阶.md\":\"db998248\",\"消息中间件_kafka.md\":\"b747dabf\",\"云原生_docker.md\":\"983c7ba7\",\"4、微服务_必备_分布式锁.md\":\"5af1cf8d\",\"消息中间件_rocketmq.md\":\"d441da85\",\"项目实战_黑马头条_基础篇.md\":\"b05af3a6\",\"ssm_springmvc.md\":\"81b9714f\",\"项目实战_支付.md\":\"1d7407dd\",\"项目实战_黑马头条_进阶篇2.md\":\"bff0015b\",\"项目实战_黑马头条_进阶篇.md\":\"19f18388\",\"java学前端_vue2_组件.md\":\"58c6b1df\",\"3、springboot_基础篇.md\":\"529c66f4\",\"3、springboot_应用篇.md\":\"8b92aa61\",\"项目实战_黑马头条_高级篇.md\":\"227c08c1\",\"1、学前端_5、小程序_uniapp.md\":\"71a282b4\",\"项目实战_云尚办公_基础篇.md\":\"1fe188ba\",\"并发 _ 多线程_并发完善.md\":\"26619c46\",\"1、学前端_2、js_ts_js 进阶.md\":\"657dfb8f\",\"java_java高级.md\":\"23782d1a\",\"java_java基础.md\":\"86d67c77\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"VitePress\",\"description\":\"A VitePress site\",\"base\":\"/notebook/\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"algolia\":{\"appId\":\"DW7O63I9IR\",\"apiKey\":\"f8ed758cdb288a8b06542bc35923c1a1\",\"indexName\":\"notebook\"},\"sidebar\":[{\"text\":\"Java\",\"collapsed\":true,\"items\":[{\"text\":\"Java基础\",\"link\":\"/Java/Java基础\"},{\"text\":\"Java新特性\",\"link\":\"/Java/Java新特性\"},{\"text\":\"Java进阶\",\"link\":\"/Java/Java进阶\"},{\"text\":\"Java集合\",\"link\":\"/Java/Java集合\"},{\"text\":\"Java高级\",\"link\":\"/Java/Java高级\"}]},{\"text\":\"Linux\",\"collapsed\":true,\"items\":[{\"text\":\"Linux基础\",\"link\":\"/Linux/Linux基础\"},{\"text\":\"Linux新特性\",\"link\":\"/Linux/Linux进阶\"},{\"text\":\"Shell脚本\",\"link\":\"/Linux/Shell\"},{\"text\":\"实用脚本\",\"link\":\"/Linux/实用脚本\"},{\"text\":\"软件部署\",\"link\":\"/Linux/软件部署\"}]},{\"text\":\"Nginx\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/Nginx/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/Nginx/进阶篇\"},{\"text\":\"实战篇\",\"link\":\"/Nginx/实战篇\"},{\"text\":\"面试篇\",\"link\":\"/Nginx/面试篇\"}]},{\"text\":\"SSM\",\"collapsed\":true,\"items\":[{\"text\":\"Maven\",\"link\":\"/SSM/Maven\"},{\"text\":\"Spring\",\"link\":\"/SSM/Spring\"},{\"text\":\"SpringMVC\",\"link\":\"/SSM/SpringMVC\"},{\"text\":\"SpringBatch\",\"link\":\"/SSM/SpringBatch\"}]},{\"text\":\"SpringBoot\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/3、SpringBoot/基础篇\"},{\"text\":\"应用篇\",\"link\":\"/3、SpringBoot/应用篇\"},{\"text\":\"新特性\",\"link\":\"/3、SpringBoot/新特性\"},{\"text\":\"运维&原理\",\"link\":\"/3、SpringBoot/运维&原理\"}]},{\"text\":\"SpringCloud\",\"collapsed\":true,\"items\":[{\"text\":\"SpringCloud\",\"link\":\"/4、微服务/进阶\"},{\"text\":\"Sentinel\",\"link\":\"/4、微服务/必备/Sentinel\"}]},{\"text\":\"SpringSecurity\",\"collapsed\":true,\"items\":[{\"text\":\"SpringSecurity基础篇\",\"link\":\"/4、微服务/SpringSecurity/基础篇\"},{\"text\":\"SpringSecurity进阶篇\",\"link\":\"/4、微服务/SpringSecurity/进阶篇\"},{\"text\":\"SpringSecurity高级篇\",\"link\":\"/4、微服务/SpringSecurity/高级篇\"}]},{\"text\":\"Mybatis & MybatisPlus\",\"collapsed\":true,\"items\":[{\"text\":\"Mybatis\",\"link\":\"/Mybatis&MybatisPlus/Mybatis\"},{\"text\":\"MybatisPlus\",\"link\":\"/Mybatis&MybatisPlus/MybatisPlus\"},{\"text\":\"JPA\",\"link\":\"/Mybatis&MybatisPlus/JPA\"}]},{\"text\":\"Git & ChatGPT\",\"collapsed\":true,\"items\":[{\"text\":\"Git\",\"link\":\"/5、运维/Git\"},{\"text\":\"Github\",\"link\":\"/5、运维/Github\"},{\"text\":\"ChatGPT\",\"link\":\"/5、运维/ChatGPT\"},{\"text\":\"Jenkins\",\"link\":\"/5、运维/Jenkins\"},{\"text\":\"Netty\",\"link\":\"/5、运维/Netty\"}]},{\"text\":\"数据库\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL基础\",\"link\":\"/2、数据库/MySQL/MySQL核心/基础\"},{\"text\":\"MySQL进阶\",\"link\":\"/2、数据库/MySQL/MySQL核心/进阶\"},{\"text\":\"MySQL优化\",\"link\":\"/2、数据库/MySQL/MySQL核心/优化\"},{\"text\":\"MySQL设计\",\"link\":\"/2、数据库/MySQL/MySQL核心/设计\"},{\"text\":\"MySQL运维\",\"link\":\"/2、数据库/MySQL/MySQL核心/运维\"},{\"text\":\"分库分表\",\"link\":\"/2、数据库/MySQL/分库分表\"}]},{\"text\":\"Redis\",\"collapsed\":true,\"items\":[{\"text\":\"Redis基础\",\"link\":\"/2、数据库/Redis/Redis基础\"},{\"text\":\"Redis优化\",\"link\":\"/2、数据库/Redis/Redis优化\"},{\"text\":\"Redis原理\",\"link\":\"/2、数据库/Redis/Redis原理\"},{\"text\":\"Redis高级\",\"link\":\"/2、数据库/Redis/Redis高级\"},{\"text\":\"Redis实战\",\"link\":\"/2、数据库/Redis/Redis实战\"},{\"text\":\"本地缓存\",\"link\":\"/2、数据库/Redis/本地缓存\"}]},{\"text\":\"MongoDB\",\"collapsed\":true,\"items\":[{\"text\":\"MongoDB基础\",\"link\":\"/2、数据库/MongoDB/基础\"},{\"text\":\"MongoDB进阶\",\"link\":\"/2、数据库/MongoDB/整合\"}]},{\"text\":\"ElasticSearch\",\"collapsed\":true,\"items\":[{\"text\":\"ES基础\",\"link\":\"/2、数据库/ElasticSearch/1、ES基础\"},{\"text\":\"ES高级\",\"link\":\"/2、数据库/ElasticSearch/3、ES高级\"}]},{\"text\":\"InfluxDB\",\"link\":\"/2、数据库/influxdb\"},{\"text\":\"Neo4j\",\"link\":\"/2、数据库/Neo4j\"}]},{\"text\":\"高并发 & 秒杀 & 分布式\",\"collapsed\":true,\"items\":[{\"text\":\"分布式理论\",\"link\":\"/三高/分布式\"},{\"text\":\"分布式锁\",\"link\":\"/4、微服务/必备/分布式锁\"},{\"text\":\"秒杀\",\"link\":\"/三高/秒杀\"},{\"text\":\"高可用\",\"link\":\"/三高/高可用\"},{\"text\":\"高并发\",\"link\":\"/三高/高并发\"}]},{\"text\":\"云原生\",\"collapsed\":true,\"items\":[{\"text\":\"Docker\",\"link\":\"/云原生/Docker\"},{\"text\":\"K8S\",\"link\":\"/云原生/K8S\"}]},{\"text\":\"可视化 & 监控\",\"collapsed\":true,\"items\":[{\"text\":\"监控基础\",\"link\":\"/可视化 & 监控/监控基础\"},{\"text\":\"监控进阶\",\"link\":\"/可视化 & 监控/监控进阶\"},{\"text\":\"可视化大屏\",\"link\":\"/可视化 & 监控/可视化大屏\"},{\"text\":\"Zabbix\",\"link\":\"/可视化 & 监控/Zabbix\"}]},{\"text\":\"学前端\",\"collapsed\":true,\"items\":[{\"text\":\"HTML+CSS\",\"collapsed\":true,\"items\":[{\"text\":\"HTML基础\",\"link\":\"/1、学前端/1、HTML+CSS/HTML基础\"},{\"text\":\"CSS基础\",\"link\":\"/1、学前端/1、HTML+CSS/CSS基础\"},{\"text\":\"网页进阶\",\"link\":\"/1、学前端/1、HTML+CSS/网页进阶\"}]},{\"text\":\"JS+TS\",\"collapsed\":true,\"items\":[{\"text\":\"JS基础\",\"link\":\"/1、学前端/2、JS+TS/JS 基础\"},{\"text\":\"JS进阶\",\"link\":\"/1、学前端/2、JS+TS/JS 进阶\"},{\"text\":\"ES6基础\",\"link\":\"/1、学前端/2、JS+TS/ES6 基础\"},{\"text\":\"ES6进阶\",\"link\":\"/1、学前端/2、JS+TS/ES6 进阶\"},{\"text\":\"TS基础\",\"link\":\"/1、学前端/2、JS+TS/TypeScript\"}]},{\"text\":\"NodeJS\",\"collapsed\":true,\"items\":[{\"text\":\"Node基础\",\"link\":\"/1、学前端/4、Node/基础篇\"},{\"text\":\"Node进阶\",\"link\":\"/1、学前端/4、Node/进阶篇\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/4、Node/项目实战\"}]},{\"text\":\"Vue\",\"collapsed\":true,\"items\":[{\"text\":\"Vue3进阶\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3进阶\"},{\"text\":\"Vue3高级\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3高级\"},{\"text\":\"Vue3新语法\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3新语法\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/3、Vue/Vue2/Vue2项目\"}]},{\"text\":\"小程序\",\"collapsed\":true,\"items\":[{\"text\":\"小程序基础\",\"link\":\"/1、学前端/5、小程序/微信小程序\"},{\"text\":\"小程序优化\",\"link\":\"/1、学前端/5、小程序/小程序优化\"},{\"text\":\"uniapp\",\"link\":\"/1、学前端/5、小程序/uniapp\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/5、小程序/小程序项目\"}]}]},{\"text\":\"计算机基础\",\"collapsed\":true,\"items\":[{\"text\":\"数据结构\",\"link\":\"/计算机基础/数据结构/基础篇\"},{\"text\":\"操作系统\",\"link\":\"/计算机基础/计算机基础/操作系统\"},{\"text\":\"设计模式\",\"link\":\"/计算机基础/设计模式/基础篇\"},{\"text\":\"计算机网络\",\"link\":\"/计算机基础/计算机网络/网络基础\"},{\"text\":\"UML\",\"link\":\"/计算机基础/设计模式/UML\"},{\"text\":\"LeetCode\",\"link\":\"/计算机基础/算法/LeetCode\"}]},{\"text\":\"项目实战\",\"collapsed\":true,\"items\":[{\"text\":\"云尚办公\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/云尚办公/基础篇\"}]},{\"text\":\"小兔鲜\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/小兔鲜/基础篇\"},{\"text\":\"进阶篇1\",\"link\":\"/项目实战/小兔鲜/进阶篇1\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/小兔鲜/进阶篇2\"}]},{\"text\":\"地图\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/百度地图/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/百度地图/进阶篇\"}]},{\"text\":\"苍穹外卖\",\"collapsed\":true,\"items\":[{\"text\":\"进阶篇\",\"link\":\"/项目实战/苍穹外卖/进阶篇\"}]},{\"text\":\"黑马头条\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/黑马头条/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/黑马头条/进阶篇\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/黑马头条/进阶篇2\"},{\"text\":\"高级篇\",\"link\":\"/项目实战/黑马头条/高级篇\"}]},{\"text\":\"支付\",\"link\":\"/项目实战/支付\"},{\"text\":\"项目推荐\",\"link\":\"/项目实战/项目推荐\"}]},{\"text\":\"团队成员\",\"link\":\"/team\"}],\"siteTitle\":\"任硕的文档\",\"logo\":\"/Vue.png\",\"nav\":[{\"text\":\"Java学前端\",\"items\":[{\"items\":[{\"text\":\"HTML+JS\",\"link\":\"/Java学前端/HTML+JS\"},{\"text\":\"CSS\",\"link\":\"/Java学前端/CSS\"},{\"text\":\"Vue2+组件\",\"link\":\"/Java学前端/Vue2+组件\"},{\"text\":\"Vue3+组件\",\"link\":\"/Java学前端/Vue3+组件\"},{\"text\":\"React\",\"link\":\"/Java学前端/React\"}]}],\"activeMatch\":\"/Java/\"},{\"text\":\"软件测试\",\"items\":[{\"items\":[{\"text\":\"测试基础\",\"link\":\"/软件测试/测试基础\"},{\"text\":\"压力测试\",\"link\":\"/软件测试/压力测试\"}]}]},{\"text\":\"多线程\",\"items\":[{\"items\":[{\"text\":\"基础篇\",\"link\":\"/并发 & 多线程/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/并发 & 多线程/并发完善\"}]}]},{\"text\":\"开发工具\",\"items\":[{\"items\":[{\"text\":\"Chrome\",\"link\":\"/IDEA/Chrome\"},{\"text\":\"IDEA基础\",\"link\":\"/IDEA/IDEA基础\"},{\"text\":\"IDEA插件\",\"link\":\"/IDEA/IDEA插件\"},{\"text\":\"VS Code\",\"link\":\"/IDEA/VS Code\"}]}]},{\"text\":\"消息中间件\",\"items\":[{\"items\":[{\"text\":\"RabbitMQ\",\"link\":\"/消息中间件/RabbitMQ\"},{\"text\":\"RocketMQ\",\"link\":\"/消息中间件/RocketMQ\"},{\"text\":\"Kafka\",\"link\":\"/消息中间件/Kafka\"},{\"text\":\"Canal\",\"link\":\"/消息中间件/Canal\"}]}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/renshuo123/renshuo123.github.io\"},{\"icon\":\"twitter\",\"link\":\"#\"},{\"icon\":{\"svg\":\"<svg t=\\\"1676028692954\\\" class=\\\"icon\\\" ...</path></svg>\"},\"link\":\"https://github.com/\"}]},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>